Skip to content

Mono-Doc

This file holds the entire API reference in a single page; should that be your preference.


Clients

Client

Bases: processors.AutoModEvents, processors.ChannelEvents, processors.EntitlementEvents, processors.GuildEvents, processors.IntegrationEvents, processors.MemberEvents, processors.MessageEvents, processors.ReactionEvents, processors.RoleEvents, processors.ScheduledEvents, processors.StageEvents, processors.ThreadEvents, processors.UserEvents, processors.VoiceEvents

The bot client.

Parameters:

Name Type Description Default
intents Union[int, Intents]

The intents to use

Intents.DEFAULT
status Status

The status the bot should log in with (IE ONLINE, DND, IDLE)

Status.ONLINE
activity Union[Activity, str]

The activity the bot should log in "playing"

None
sync_interactions bool

Should application commands be synced with discord?

True
delete_unused_application_cmds bool

Delete any commands from discord that aren't implemented in this client

False
enforce_interaction_perms bool

Enforce discord application command permissions, locally

True
fetch_members bool

Should the client fetch members from guilds upon startup (this will delay the client being ready)

False
send_command_tracebacks bool

Automatically send uncaught tracebacks if a command throws an exception

True
send_not_ready_messages bool

Send a message to the user if they try to use a command before the client is ready

False
auto_defer Absent[Union[AutoDefer, bool]]
MISSING
interaction_context Type[InteractionContext]

Type[InteractionContext]: InteractionContext: The object to instantiate for Interaction Context

InteractionContext
component_context Type[BaseContext]

Type[ComponentContext]: The object to instantiate for Component Context

ComponentContext
autocomplete_context Type[BaseContext]

Type[AutocompleteContext]: The object to instantiate for Autocomplete Context

AutocompleteContext
modal_context Type[BaseContext]

Type[ModalContext]: The object to instantiate for Modal Context

ModalContext
total_shards int

The total number of shards in use

1
shard_id int

The zero based int ID of this shard

0
debug_scope Absent[Snowflake_Type]

Force all application commands to be registered within this scope

MISSING
disable_dm_commands bool

Should interaction commands be disabled in DMs?

False
basic_logging bool

Utilise basic logging to output library data to console. Do not use in combination with Client.logger

False
logging_level int

The level of logging to use for basic_logging. Do not use in combination with Client.logger

logging.INFO
logger logging.Logger

The logger interactions.py should use. Do not use in combination with Client.basic_logging and Client.logging_level. Note: Different loggers with multiple clients are not supported

MISSING
proxy

A http/https proxy to use for all requests

required
proxy_auth BasicAuth | tuple[str, str] | None

The auth to use for the proxy - must be either a tuple of (username, password) or aiohttp.BasicAuth

None

Optionally, you can configure the caches here, by specifying the name of the cache, followed by a dict-style object to use. It is recommended to use smart_cache.create_cache to configure the cache here. as an example, this is a recommended attribute message_cache=create_cache(250, 50),

Intents Note

By default, all non-privileged intents will be enabled

Caching Note

Setting a message cache hard limit to None is not recommended, as it could result in extremely high memory usage, we suggest a sane limit.

Source code in interactions/client/client.py
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
class Client(
    processors.AutoModEvents,
    processors.ChannelEvents,
    processors.EntitlementEvents,
    processors.GuildEvents,
    processors.IntegrationEvents,
    processors.MemberEvents,
    processors.MessageEvents,
    processors.ReactionEvents,
    processors.RoleEvents,
    processors.ScheduledEvents,
    processors.StageEvents,
    processors.ThreadEvents,
    processors.UserEvents,
    processors.VoiceEvents,
):
    """

    The bot client.

    Args:
        intents: The intents to use

        status: The status the bot should log in with (IE ONLINE, DND, IDLE)
        activity: The activity the bot should log in "playing"

        sync_interactions: Should application commands be synced with discord?
        delete_unused_application_cmds: Delete any commands from discord that aren't implemented in this client
        enforce_interaction_perms: Enforce discord application command permissions, locally
        fetch_members: Should the client fetch members from guilds upon startup (this will delay the client being ready)
        send_command_tracebacks: Automatically send uncaught tracebacks if a command throws an exception
        send_not_ready_messages: Send a message to the user if they try to use a command before the client is ready

        auto_defer: AutoDefer: A system to automatically defer commands after a set duration
        interaction_context: Type[InteractionContext]: InteractionContext: The object to instantiate for Interaction Context
        component_context: Type[ComponentContext]: The object to instantiate for Component Context
        autocomplete_context: Type[AutocompleteContext]: The object to instantiate for Autocomplete Context
        modal_context: Type[ModalContext]: The object to instantiate for Modal Context

        total_shards: The total number of shards in use
        shard_id: The zero based int ID of this shard

        debug_scope: Force all application commands to be registered within this scope
        disable_dm_commands: Should interaction commands be disabled in DMs?
        basic_logging: Utilise basic logging to output library data to console. Do not use in combination with `Client.logger`
        logging_level: The level of logging to use for basic_logging. Do not use in combination with `Client.logger`
        logger: The logger interactions.py should use. Do not use in combination with `Client.basic_logging` and `Client.logging_level`. Note: Different loggers with multiple clients are not supported

        proxy: A http/https proxy to use for all requests
        proxy_auth: The auth to use for the proxy - must be either a tuple of (username, password) or aiohttp.BasicAuth

    Optionally, you can configure the caches here, by specifying the name of the cache, followed by a dict-style object to use.
    It is recommended to use `smart_cache.create_cache` to configure the cache here.
    as an example, this is a recommended attribute `message_cache=create_cache(250, 50)`,

    ???+ note "Intents Note"
        By default, all non-privileged intents will be enabled

    ???+ note "Caching Note"
        Setting a message cache hard limit to None is not recommended, as it could result in extremely high memory usage, we suggest a sane limit.


    """

    def __init__(
        self,
        *,
        activity: Union[Activity, str] = None,
        auto_defer: Absent[Union[AutoDefer, bool]] = MISSING,
        autocomplete_context: Type[BaseContext] = AutocompleteContext,
        basic_logging: bool = False,
        component_context: Type[BaseContext] = ComponentContext,
        context_menu_context: Type[BaseContext] = ContextMenuContext,
        debug_scope: Absent["Snowflake_Type"] = MISSING,
        delete_unused_application_cmds: bool = False,
        disable_dm_commands: bool = False,
        enforce_interaction_perms: bool = True,
        fetch_members: bool = False,
        global_post_run_callback: Absent[Callable[..., Coroutine]] = MISSING,
        global_pre_run_callback: Absent[Callable[..., Coroutine]] = MISSING,
        intents: Union[int, Intents] = Intents.DEFAULT,
        interaction_context: Type[InteractionContext] = InteractionContext,
        logger: logging.Logger = MISSING,
        logging_level: int = logging.INFO,
        modal_context: Type[BaseContext] = ModalContext,
        owner_ids: Iterable["Snowflake_Type"] = (),
        send_command_tracebacks: bool = True,
        send_not_ready_messages: bool = False,
        shard_id: int = 0,
        show_ratelimit_tracebacks: bool = False,
        slash_context: Type[BaseContext] = SlashContext,
        status: Status = Status.ONLINE,
        sync_ext: bool = True,
        sync_interactions: bool = True,
        proxy_url: str | None = None,
        proxy_auth: BasicAuth | tuple[str, str] | None = None,
        token: str | None = None,
        total_shards: int = 1,
        **kwargs,
    ) -> None:
        if logger is MISSING:
            logger = constants.get_logger()

        if basic_logging:
            logging.basicConfig()
            logger.setLevel(logging_level)

        # Set Up logger and overwrite the constant
        self.logger = logger
        """The logger interactions.py should use. Do not use in combination with `Client.basic_logging` and `Client.logging_level`.
        !!! note
            Different loggers with multiple clients are not supported"""
        constants._logger = logger

        # Configuration
        self.sync_interactions: bool = sync_interactions
        """Should application commands be synced"""
        self.del_unused_app_cmd: bool = delete_unused_application_cmds
        """Should unused application commands be deleted?"""
        self.sync_ext: bool = sync_ext
        """Should we sync whenever a extension is (un)loaded"""
        self.debug_scope = to_snowflake(debug_scope) if debug_scope is not MISSING else MISSING
        """Sync global commands as guild for quicker command updates during debug"""
        self.send_command_tracebacks: bool = send_command_tracebacks
        """Should the traceback of command errors be sent in reply to the command invocation"""
        self.send_not_ready_messages: bool = send_not_ready_messages
        """Should the bot send a message when it is not ready yet in response to a command invocation"""
        if auto_defer is True:
            auto_defer = AutoDefer(enabled=True)
        else:
            auto_defer = auto_defer or AutoDefer()
        self.auto_defer = auto_defer
        """A system to automatically defer commands after a set duration"""
        self.intents = intents if isinstance(intents, Intents) else Intents(intents)

        # resources
        if isinstance(proxy_auth, tuple):
            proxy_auth = BasicAuth(*proxy_auth)

        proxy = (proxy_url, proxy_auth) if proxy_url or proxy_auth else None
        self.http: HTTPClient = HTTPClient(
            logger=self.logger, show_ratelimit_tracebacks=show_ratelimit_tracebacks, proxy=proxy
        )
        """The HTTP client to use when interacting with discord endpoints"""

        # context factories
        self.interaction_context: Type[BaseContext[Self]] = interaction_context
        """The object to instantiate for Interaction Context"""
        self.component_context: Type[BaseContext[Self]] = component_context
        """The object to instantiate for Component Context"""
        self.autocomplete_context: Type[BaseContext[Self]] = autocomplete_context
        """The object to instantiate for Autocomplete Context"""
        self.modal_context: Type[BaseContext[Self]] = modal_context
        """The object to instantiate for Modal Context"""
        self.slash_context: Type[BaseContext[Self]] = slash_context
        """The object to instantiate for Slash Context"""
        self.context_menu_context: Type[BaseContext[Self]] = context_menu_context
        """The object to instantiate for Context Menu Context"""

        self.token: str | None = token

        # flags
        self._ready = asyncio.Event()
        self._closed = False
        self._startup = False
        self.disable_dm_commands = disable_dm_commands

        self._guild_event = asyncio.Event()
        self.guild_event_timeout = 3
        """How long to wait for guilds to be cached"""

        # Sharding
        self.total_shards = total_shards
        self._connection_state: ConnectionState = ConnectionState(self, intents, shard_id=shard_id)

        self.enforce_interaction_perms = enforce_interaction_perms

        self.fetch_members = fetch_members
        """Fetch the full members list of all guilds on startup"""

        self._mention_reg = MISSING

        # caches
        self.cache: GlobalCache = GlobalCache(self, **{k: v for k, v in kwargs.items() if hasattr(GlobalCache, k)})
        # these store the last sent presence data for change_presence
        self._status: Status = status
        if isinstance(activity, str):
            self._activity = Activity.create(name=str(activity))
        else:
            self._activity: Activity = activity

        self._user: Absent[ClientUser] = MISSING
        self._app: Absent[Application] = MISSING

        # collections
        self.interactions_by_scope: Dict["Snowflake_Type", Dict[str, InteractionCommand]] = {}
        """A dictionary of registered application commands: `{scope: [commands]}`"""
        self._interaction_lookup: dict[str, InteractionCommand] = {}
        """A dictionary of registered application commands: `{name: command}`"""
        self.interaction_tree: Dict["Snowflake_Type", Dict[str, InteractionCommand | Dict[str, InteractionCommand]]] = (
            {}
        )
        """A dictionary of registered application commands in a tree"""
        self._component_callbacks: Dict[str, Callable[..., Coroutine]] = {}
        self._regex_component_callbacks: Dict[re.Pattern, Callable[..., Coroutine]] = {}
        self._modal_callbacks: Dict[str, Callable[..., Coroutine]] = {}
        self._regex_modal_callbacks: Dict[re.Pattern, Callable[..., Coroutine]] = {}
        self._global_autocompletes: Dict[str, GlobalAutoComplete] = {}
        self.processors: Dict[str, Callable[..., Coroutine]] = {}
        self.__modules = {}
        self.ext: Dict[str, Extension] = {}
        """A dictionary of mounted ext"""
        self.listeners: Dict[str, list[Listener]] = {}
        self.waits: Dict[str, List] = {}
        self.owner_ids: set[Snowflake_Type] = set(owner_ids)

        self.async_startup_tasks: list[tuple[Callable[..., Coroutine], Iterable[Any], dict[str, Any]]] = []
        """A list of coroutines to run during startup"""

        self._add_command_hook: list[Callable[[Callable], Any]] = []

        # callbacks
        if global_pre_run_callback:
            if asyncio.iscoroutinefunction(global_pre_run_callback):
                self.pre_run_callback: Callable[..., Coroutine] = global_pre_run_callback
            else:
                raise TypeError("Callback must be a coroutine")
        else:
            self.pre_run_callback = MISSING

        if global_post_run_callback:
            if asyncio.iscoroutinefunction(global_post_run_callback):
                self.post_run_callback: Callable[..., Coroutine] = global_post_run_callback
            else:
                raise TypeError("Callback must be a coroutine")
        else:
            self.post_run_callback = MISSING

        super().__init__()
        self._sanity_check()

    async def __aenter__(self) -> "Client":
        if not self.token:
            raise ValueError(
                "Token not found - to use the bot in a context manager, you must pass the token in the Client"
                " constructor."
            )
        await self.login(self.token)
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        if not self.is_closed:
            await self.stop()

    @property
    def is_closed(self) -> bool:
        """Returns True if the bot has closed."""
        return self._closed

    @property
    def is_ready(self) -> bool:
        """Returns True if the bot is ready."""
        return self._ready.is_set()

    @property
    def latency(self) -> float:
        """Returns the latency of the websocket connection (seconds)."""
        return self._connection_state.latency

    @property
    def average_latency(self) -> float:
        """Returns the average latency of the websocket connection (seconds)."""
        return self._connection_state.average_latency

    @property
    def start_time(self) -> datetime:
        """The start time of the bot."""
        return self._connection_state.start_time

    @property
    def gateway_started(self) -> bool:
        """Returns if the gateway has been started."""
        return self._connection_state.gateway_started.is_set()

    @property
    def user(self) -> ClientUser:
        """Returns the bot's user."""
        return self._user

    @property
    def app(self) -> Application:
        """Returns the bots application."""
        return self._app

    @property
    def owner(self) -> Optional["User"]:
        """Returns the bot's owner'."""
        try:
            return self.app.owner
        except TypeError:
            return MISSING

    @property
    def owners(self) -> List["User"]:
        """Returns the bot's owners as declared via `client.owner_ids`."""
        return [self.get_user(u_id) for u_id in self.owner_ids]

    @property
    def guilds(self) -> List["Guild"]:
        """Returns a list of all guilds the bot is in."""
        return self.user.guilds

    @property
    def status(self) -> Status:
        """
        Get the status of the bot.

        IE online, afk, dnd

        """
        return self._status

    @property
    def activity(self) -> Activity:
        """Get the activity of the bot."""
        return self._activity

    @property
    def application_commands(self) -> List[InteractionCommand]:
        """A list of all application commands registered within the bot."""
        commands = []
        for scope in self.interactions_by_scope.keys():
            commands += [cmd for cmd in self.interactions_by_scope[scope].values() if cmd not in commands]

        return commands

    @property
    def ws(self) -> GatewayClient:
        """Returns the websocket client."""
        return self._connection_state.gateway

    def get_guild_websocket(self, id: "Snowflake_Type") -> GatewayClient:
        return self.ws

    def _sanity_check(self) -> None:
        """Checks for possible and common errors in the bot's configuration."""
        self.logger.debug("Running client sanity checks...")

        contexts = {
            self.interaction_context: InteractionContext,
            self.component_context: ComponentContext,
            self.autocomplete_context: AutocompleteContext,
            self.modal_context: ModalContext,
        }
        for obj, expected in contexts.items():
            if not issubclass(obj, expected):
                raise TypeError(f"{obj.__name__} must inherit from {expected.__name__}")

        if self.del_unused_app_cmd:
            self.logger.warning(
                "As `delete_unused_application_cmds` is enabled, the client must cache all guilds app-commands, this"
                " could take a while."
            )

        if Intents.GUILDS not in self._connection_state.intents:
            self.logger.warning("GUILD intent has not been enabled; this is very likely to cause errors")

        if self.fetch_members and Intents.GUILD_MEMBERS not in self._connection_state.intents:
            raise BotException("Members Intent must be enabled in order to use fetch members")
        if self.fetch_members:
            self.logger.warning("fetch_members enabled; startup will be delayed")

        if len(self.processors) == 0:
            self.logger.warning("No Processors are loaded! This means no events will be processed!")

        caches = [
            c[0]
            for c in inspect.getmembers(self.cache, predicate=lambda x: isinstance(x, dict))
            if not c[0].startswith("__")
        ]
        for cache in caches:
            _cache_obj = getattr(self.cache, cache)
            if isinstance(_cache_obj, NullCache):
                self.logger.warning(f"{cache} has been disabled")

    def _queue_task(self, coro: Listener, event: BaseEvent, *args, **kwargs) -> asyncio.Task:
        async def _async_wrap(_coro: Listener, _event: BaseEvent, *_args, **_kwargs) -> None:
            try:
                if (
                    not isinstance(_event, (events.Error, events.RawGatewayEvent))
                    and coro.delay_until_ready
                    and not self.is_ready
                ):
                    await self.wait_until_ready()

                # don't pass event object if listener doesn't expect it
                if _coro.pass_event_object:
                    await _coro(_event, *_args, **_kwargs)
                else:
                    if not _coro.warned_no_event_arg and len(_event.__attrs_attrs__) > 2 and _coro.event != "event":
                        self.logger.warning(
                            f"{_coro} is listening to {_coro.event} event which contains event data. "
                            f"Add an event argument to this listener to receive the event data object."
                        )
                        _coro.warned_no_event_arg = True
                    await _coro()
            except asyncio.CancelledError:
                pass
            except Exception as e:
                if isinstance(event, events.Error):
                    # No infinite loops please
                    self.default_error_handler(repr(event), e)
                else:
                    self.dispatch(events.Error(source=repr(event), error=e))

        try:
            asyncio.get_running_loop()
            return asyncio.create_task(
                _async_wrap(coro, event, *args, **kwargs), name=f"interactions:: {event.resolved_name}"
            )
        except RuntimeError:
            self.logger.debug("Event loop is closed; queuing task for execution on startup")
            self.async_startup_tasks.append((_async_wrap, (coro, event, *args), kwargs))

    @staticmethod
    def default_error_handler(source: str, error: BaseException) -> None:
        """
        The default error logging behaviour.

        Args:
            source: The source of this error
            error: The exception itself

        """
        out = traceback.format_exception(error)

        if isinstance(error, HTTPException):
            # HTTPException's are of 3 known formats, we can parse them for human readable errors
            with contextlib.suppress(Exception):
                out = [str(error)]
        get_logger().error(
            "Ignoring exception in {}:{}{}".format(source, "\n" if len(out) > 1 else " ", "".join(out)),
        )

    @Listener.create(is_default_listener=True)
    async def on_error(self, event: events.Error) -> None:
        """
        Catches all errors dispatched by the library.

        By default it will format and print them to console.

        Listen to the `Error` event to overwrite this behaviour.

        """
        self.default_error_handler(event.source, event.error)

    @Listener.create(is_default_listener=True)
    async def on_command_error(self, event: events.CommandError) -> None:
        """
        Catches all errors dispatched by commands.

        By default it will dispatch the `Error` event.

        Listen to the `CommandError` event to overwrite this behaviour.

        """
        self.dispatch(
            events.Error(
                source=f"cmd `/{event.ctx.invoke_target}`",
                error=event.error,
                args=event.args,
                kwargs=event.kwargs,
                ctx=event.ctx,
            )
        )
        with contextlib.suppress(errors.LibraryException):
            if isinstance(event.error, errors.CommandOnCooldown):
                await event.ctx.send(
                    embeds=Embed(
                        description=(
                            "This command is on cooldown!\n"
                            f"Please try again in {int(event.error.cooldown.get_cooldown_time())} seconds"
                        ),
                        color=BrandColors.FUCHSIA,
                    )
                )
            elif isinstance(event.error, errors.MaxConcurrencyReached):
                await event.ctx.send(
                    embeds=Embed(
                        description="This command has reached its maximum concurrent usage!\nPlease try again shortly.",
                        color=BrandColors.FUCHSIA,
                    )
                )
            elif isinstance(event.error, errors.CommandCheckFailure):
                await event.ctx.send(
                    embeds=Embed(
                        description="You do not have permission to run this command!",
                        color=BrandColors.YELLOW,
                    )
                )
            elif self.send_command_tracebacks:
                out = "".join(traceback.format_exception(event.error))
                if self.http.token is not None:
                    out = out.replace(self.http.token, "[REDACTED TOKEN]")
                await event.ctx.send(
                    embeds=Embed(
                        title=f"Error: {type(event.error).__name__}",
                        color=BrandColors.RED,
                        description=f"```\n{out[:EMBED_MAX_DESC_LENGTH - 8]}```",
                    )
                )

    @Listener.create(is_default_listener=True)
    async def on_command_completion(self, event: events.CommandCompletion) -> None:
        """
        Called *after* any command is ran.

        By default, it will simply log the command.

        Listen to the `CommandCompletion` event to overwrite this behaviour.

        """
        self.logger.info(f"Command Called: {event.ctx.invoke_target} with {event.ctx.args = } | {event.ctx.kwargs = }")

    @Listener.create(is_default_listener=True)
    async def on_component_error(self, event: events.ComponentError) -> None:
        """
        Catches all errors dispatched by components.

        By default it will dispatch the `Error` event.

        Listen to the `ComponentError` event to overwrite this behaviour.

        """
        self.dispatch(
            events.Error(
                source=f"Component Callback for {event.ctx.custom_id}",
                error=event.error,
                args=event.args,
                kwargs=event.kwargs,
                ctx=event.ctx,
            )
        )

    @Listener.create(is_default_listener=True)
    async def on_component_completion(self, event: events.ComponentCompletion) -> None:
        """
        Called *after* any component callback is ran.

        By default, it will simply log the component use.

        Listen to the `ComponentCompletion` event to overwrite this behaviour.

        """
        symbol = "¢"
        self.logger.info(
            f"Component Called: {symbol}{event.ctx.invoke_target} with {event.ctx.args = } | {event.ctx.kwargs = }"
        )

    @Listener.create(is_default_listener=True)
    async def on_autocomplete_error(self, event: events.AutocompleteError) -> None:
        """
        Catches all errors dispatched by autocompletion options.

        By default it will dispatch the `Error` event.

        Listen to the `AutocompleteError` event to overwrite this behaviour.

        """
        self.dispatch(
            events.Error(
                source=f"Autocomplete Callback for /{event.ctx.invoke_target} - Option: {event.ctx.focussed_option}",
                error=event.error,
                args=event.args,
                kwargs=event.kwargs,
                ctx=event.ctx,
            )
        )

    @Listener.create(is_default_listener=True)
    async def on_autocomplete_completion(self, event: events.AutocompleteCompletion) -> None:
        """
        Called *after* any autocomplete callback is ran.

        By default, it will simply log the autocomplete callback.

        Listen to the `AutocompleteCompletion` event to overwrite this behaviour.

        """
        symbol = "$"
        self.logger.info(
            f"Autocomplete Called: {symbol}{event.ctx.invoke_target} with {event.ctx.focussed_option = } |"
            f" {event.ctx.kwargs = }"
        )

    @Listener.create(is_default_listener=True)
    async def on_modal_error(self, event: events.ModalError) -> None:
        """
        Catches all errors dispatched by modals.

        By default it will dispatch the `Error` event.

        Listen to the `ModalError` event to overwrite this behaviour.

        """
        self.dispatch(
            events.Error(
                source=f"Modal Callback for custom_id {event.ctx.custom_id}",
                error=event.error,
                args=event.args,
                kwargs=event.kwargs,
                ctx=event.ctx,
            )
        )

    @Listener.create(is_default_listener=True)
    async def on_modal_completion(self, event: events.ModalCompletion) -> None:
        """
        Called *after* any modal callback is ran.

        By default, it will simply log the modal callback.

        Listen to the `ModalCompletion` event to overwrite this behaviour.

        """
        self.logger.info(f"Modal Called: {event.ctx.custom_id = } with {event.ctx.responses = }")

    @Listener.create()
    async def on_resume(self) -> None:
        self._ready.set()

    @Listener.create(is_default_listener=True)
    async def _on_websocket_ready(self, event: events.RawGatewayEvent) -> None:
        """
        Catches websocket ready and determines when to dispatch the client `READY` signal.

        Args:
            event: The websocket ready packet

        """
        data = event.data
        expected_guilds = {to_snowflake(guild["id"]) for guild in data["guilds"]}
        self._user._add_guilds(expected_guilds)

        if not self._startup:
            while len(self.guilds) != len(expected_guilds):
                try:  # wait to let guilds cache
                    await asyncio.wait_for(self._guild_event.wait(), self.guild_event_timeout)
                except asyncio.TimeoutError:
                    # this will *mostly* occur when a guild has been shadow deleted by discord T&S.
                    # there is no way to check for this, so we just need to wait for this to time out.
                    # We still log it though, just in case.
                    self.logger.debug("Timeout waiting for guilds cache")
                    break
                self._guild_event.clear()

            if self.fetch_members:
                # ensure all guilds have completed chunking
                for guild in self.guilds:
                    if guild and not guild.chunked.is_set():
                        self.logger.debug(f"Waiting for {guild.id} to chunk")
                        await guild.chunked.wait()

            # cache slash commands
            if not self._startup:
                await self._init_interactions()

            self._startup = True
            self.dispatch(events.Startup())

        else:
            # reconnect ready
            ready_guilds = set()

            async def _temp_listener(_event: events.RawGatewayEvent) -> None:
                ready_guilds.add(_event.data["id"])

            listener = Listener.create("_on_raw_guild_create")(_temp_listener)
            self.add_listener(listener)

            while len(ready_guilds) != len(expected_guilds):
                try:
                    await asyncio.wait_for(self._guild_event.wait(), self.guild_event_timeout)
                except asyncio.TimeoutError:
                    break
                self._guild_event.clear()

            self.listeners["raw_guild_create"].remove(listener)

        self._ready.set()
        self.dispatch(events.Ready())

    async def login(self, token: str | None = None) -> None:
        """
        Login to discord via http.

        !!! note
            You will need to run Client.start_gateway() before you start receiving gateway events.

        Args:
            token str: Your bot's token

        """
        if not self.token and not token:
            raise RuntimeError(
                "No token provided - please provide a token in the client constructor or via the login method."
            )
        self.token = (token or self.token).strip()

        # i needed somewhere to put this call,
        # login will always run after initialisation
        # so im gathering commands here
        self._gather_callbacks()

        if any(v for v in constants.CLIENT_FEATURE_FLAGS.values()):
            # list all enabled flags
            enabled_flags = [k for k, v in constants.CLIENT_FEATURE_FLAGS.items() if v]
            self.logger.info(f"Enabled feature flags: {', '.join(enabled_flags)}")

        self.logger.debug("Attempting to login")
        me = await self.http.login(self.token)
        self._user = ClientUser.from_dict(me, self)
        self.cache.place_user_data(me)
        self._app = Application.from_dict(await self.http.get_current_bot_information(), self)
        self._mention_reg = re.compile(rf"^(<@!?{self.user.id}*>\s)")

        if self.app.owner:
            self.owner_ids.add(self.app.owner.id)

        self.dispatch(events.Login())

    async def astart(self, token: str | None = None) -> None:
        """
        Asynchronous method to start the bot.

        Args:
            token: Your bot's token

        """
        await self.login(token)

        # run any pending startup tasks
        if self.async_startup_tasks:
            try:
                await asyncio.gather(
                    *[
                        task[0](*task[1] if len(task) > 1 else [], **task[2] if len(task) == 3 else {})
                        for task in self.async_startup_tasks
                    ]
                )
            except Exception as e:
                self.dispatch(events.Error(source="async-extension-loader", error=e))
        try:
            await self._connection_state.start()
        finally:
            await self.stop()

    def start(self, token: str | None = None) -> None:
        """
        Start the bot.

        If `uvloop` is installed, it will be used.

        info:
            This is the recommended method to start the bot
        """
        try:
            import uvloop

            has_uvloop = True
        except ImportError:
            has_uvloop = False

        with contextlib.suppress(KeyboardInterrupt):
            if has_uvloop:
                self.logger.info("uvloop is installed, using it")
                if sys.version_info >= (3, 11):
                    with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner:
                        runner.run(self.astart(token))
                else:
                    uvloop.install()
                    asyncio.run(self.astart(token))
            else:
                asyncio.run(self.astart(token))

    async def start_gateway(self) -> None:
        """Starts the gateway connection."""
        try:
            await self._connection_state.start()
        finally:
            await self.stop()

    async def stop(self) -> None:
        """Shutdown the bot."""
        self.logger.debug("Stopping the bot.")
        self._ready.clear()
        await self.http.close()
        await self._connection_state.stop()

    async def _process_waits(self, event: events.BaseEvent) -> None:
        if _waits := self.waits.get(event.resolved_name, []):
            index_to_remove = []
            for i, _wait in enumerate(_waits):
                result = await _wait(event)
                if result:
                    index_to_remove.append(i)

            for idx in sorted(index_to_remove, reverse=True):
                _waits.pop(idx)

    def dispatch(self, event: events.BaseEvent, *args, **kwargs) -> None:
        """
        Dispatch an event.

        Args:
            event: The event to be dispatched.

        """
        if listeners := self.listeners.get(event.resolved_name, []):
            self.logger.debug(f"Dispatching Event: {event.resolved_name}")
            event.bot = self
            for _listen in listeners:
                try:
                    self._queue_task(_listen, event, *args, **kwargs)
                except Exception as e:
                    raise BotException(
                        f"An error occurred attempting during {event.resolved_name} event processing"
                    ) from e

        try:
            asyncio.get_running_loop()
            _ = asyncio.create_task(self._process_waits(event))  # noqa: RUF006
        except RuntimeError:
            # dispatch attempt before event loop is running
            self.async_startup_tasks.append((self._process_waits, (event,), {}))

        if "event" in self.listeners:
            # special meta event listener
            for _listen in self.listeners["event"]:
                self._queue_task(_listen, event, *args, **kwargs)

    async def wait_until_ready(self) -> None:
        """Waits for the client to become ready."""
        await self._ready.wait()

    @overload
    def wait_for(
        self,
        event: type[EventT],
        checks: Absent[Callable[[EventT], bool] | Callable[[EventT], Awaitable[bool]]] = MISSING,
        timeout: Optional[float] = None,
    ) -> "Awaitable[EventT]": ...

    @overload
    def wait_for(
        self,
        event: str,
        checks: Callable[[EventT], bool] | Callable[[EventT], Awaitable[bool]],
        timeout: Optional[float] = None,
    ) -> "Awaitable[EventT]": ...

    @overload
    def wait_for(
        self,
        event: str,
        checks: Missing = MISSING,
        timeout: Optional[float] = None,
    ) -> Awaitable[Any]: ...

    def wait_for(
        self,
        event: Union[str, "type[BaseEvent]"],
        checks: Absent[Callable[[BaseEvent], bool] | Callable[[BaseEvent], Awaitable[bool]]] = MISSING,
        timeout: Optional[float] = None,
    ) -> Awaitable[Any]:
        """
        Waits for a WebSocket event to be dispatched.

        Args:
            event: The name of event to wait.
            checks: A predicate to check what to wait for.
            timeout: The number of seconds to wait before timing out.

        Returns:
            The event object.

        """
        event = get_event_name(event)

        if event not in self.waits:
            self.waits[event] = []

        future = asyncio.Future()
        self.waits[event].append(Wait(event, checks, future))

        return asyncio.wait_for(future, timeout)

    async def wait_for_modal(
        self,
        modal: "Modal",
        author: Optional["Snowflake_Type"] = None,
        timeout: Optional[float] = None,
    ) -> "ModalContext":
        """
        Wait for a modal response.

        Args:
            modal: The modal we're waiting for.
            author: The user we're waiting for to reply
            timeout: A timeout in seconds to stop waiting

        Returns:
            The context of the modal response

        Raises:
            asyncio.TimeoutError: if no response is received that satisfies the predicate before timeout seconds have passed

        """
        author = to_snowflake(author) if author else None

        def predicate(event: events.ModalCompletion) -> bool:
            if modal.custom_id != event.ctx.custom_id:
                return False
            return author == to_snowflake(event.ctx.author) if author else True

        resp = await self.wait_for("modal_completion", predicate, timeout)
        return resp.ctx

    @overload
    async def wait_for_component(
        self,
        messages: Union[Message, int, list],
        components: Union[
            List[List[Union["BaseComponent", dict]]],
            List[Union["BaseComponent", dict]],
            "BaseComponent",
            dict,
        ],
        check: Optional[Callable[[events.Component], bool] | Callable[[events.Component], Awaitable[bool]]] = None,
        timeout: Optional[float] = None,
    ) -> "events.Component": ...

    @overload
    async def wait_for_component(
        self,
        *,
        components: Union[
            List[List[Union["BaseComponent", dict]]],
            List[Union["BaseComponent", dict]],
            "BaseComponent",
            dict,
        ],
        check: Optional[Callable[[events.Component], bool] | Callable[[events.Component], Awaitable[bool]]] = None,
        timeout: Optional[float] = None,
    ) -> "events.Component": ...

    @overload
    async def wait_for_component(
        self,
        messages: None,
        components: Union[
            List[List[Union["BaseComponent", dict]]],
            List[Union["BaseComponent", dict]],
            "BaseComponent",
            dict,
        ],
        check: Optional[Callable[[events.Component], bool] | Callable[[events.Component], Awaitable[bool]]] = None,
        timeout: Optional[float] = None,
    ) -> "events.Component": ...

    @overload
    async def wait_for_component(
        self,
        messages: Union[Message, int, list],
        components: None = None,
        check: Optional[Callable[[events.Component], bool] | Callable[[events.Component], Awaitable[bool]]] = None,
        timeout: Optional[float] = None,
    ) -> "events.Component": ...

    async def wait_for_component(
        self,
        messages: Optional[Union[Message, int, list]] = None,
        components: Optional[
            Union[
                List[List[Union["BaseComponent", dict]]],
                List[Union["BaseComponent", dict]],
                "BaseComponent",
                dict,
            ]
        ] = None,
        check: Optional[Callable[[events.Component], bool] | Callable[[events.Component], Awaitable[bool]]] = None,
        timeout: Optional[float] = None,
    ) -> "events.Component":
        """
        Waits for a component to be sent to the bot.

        Args:
            messages: The message object to check for.
            components: The components to wait for.
            check: A predicate to check what to wait for.
            timeout: The number of seconds to wait before timing out.

        Returns:
            `Component` that was invoked. Use `.ctx` to get the `ComponentContext`.

        Raises:
            asyncio.TimeoutError: if timed out

        """
        if not messages and not components:
            raise ValueError("You must specify messages or components (or both)")

        message_ids = (
            to_snowflake_list(messages) if isinstance(messages, list) else to_snowflake(messages) if messages else None
        )
        custom_ids = list(get_components_ids(components)) if components else None

        # automatically convert improper custom_ids
        if custom_ids and not all(isinstance(x, str) for x in custom_ids):
            custom_ids = [str(i) for i in custom_ids]

        async def _check(event: events.Component) -> bool:
            ctx: ComponentContext = event.ctx
            # if custom_ids is empty or there is a match
            wanted_message = not message_ids or ctx.message.id in (
                [message_ids] if isinstance(message_ids, int) else message_ids
            )
            wanted_component = not custom_ids or ctx.custom_id in custom_ids
            if wanted_message and wanted_component:
                if asyncio.iscoroutinefunction(check):
                    return bool(check is None or await check(event))
                return bool(check is None or check(event))
            return False

        return await self.wait_for("component", checks=_check, timeout=timeout)

    def command(self, *args, **kwargs) -> Callable:
        """A decorator that registers a command. Aliases `interactions.slash_command`"""
        raise NotImplementedError(
            "Use interactions.slash_command instead. Please consult the v4 -> v5 migration guide https://interactions-py.github.io/interactions.py/Guides/98%20Migration%20from%204.X/"
        )

    def listen(self, event_name: Absent[str] = MISSING) -> Callable[[AsyncCallable], Listener]:
        """
        A decorator to be used in situations that the library can't automatically hook your listeners. Ideally, the standard listen decorator should be used, not this.

        Args:
            event_name: The event name to use, if not the coroutine name

        Returns:
            A listener that can be used to hook into the event.

        """

        def wrapper(coro: AsyncCallable) -> Listener:
            listener = Listener.create(event_name)(coro)
            self.add_listener(listener)
            return listener

        return wrapper

    event = listen  # alias for easier migration

    def add_event_processor(self, event_name: Absent[str] = MISSING) -> Callable[[AsyncCallable], AsyncCallable]:
        """
        A decorator to be used to add event processors.

        Args:
            event_name: The event name to use, if not the coroutine name

        Returns:
            A function that can be used to hook into the event.

        """

        def wrapper(coro: AsyncCallable) -> AsyncCallable:
            name = event_name
            if name is MISSING:
                name = coro.__name__
            name = name.lstrip("_")
            name = name.removeprefix("on_")
            self.processors[name] = coro
            return coro

        return wrapper

    def add_listener(self, listener: Listener) -> None:
        """
        Add a listener for an event, if no event is passed, one is determined.

        Args:
            listener Listener: The listener to add to the client

        """
        if listener.event == "event":
            self.logger.critical(
                f"Subscribing to `{listener.event}` - Meta Events are very expensive; remember to remove it before"
                " releasing your bot"
            )

        if not listener.is_default_listener:
            # check that the required intents are enabled

            event_class_name = "".join([name.capitalize() for name in listener.event.split("_")])
            if event_class := globals().get(event_class_name):
                if required_intents := _INTENT_EVENTS.get(event_class):
                    if all(required_intent not in self.intents for required_intent in required_intents):
                        self.logger.warning(
                            f"Event `{listener.event}` will not work since the required intent is not set -> Requires"
                            f" any of: `{required_intents}`"
                        )

        # prevent the same callback being added twice
        if listener in self.listeners.get(listener.event, []):
            self.logger.debug(f"Listener {listener} has already been hooked, not re-hooking it again")
            return

        listener.lazy_parse_params()

        if listener.event not in self.listeners:
            self.listeners[listener.event] = []
        self.listeners[listener.event].append(listener)

        # check if other listeners are to be deleted
        default_listeners = [c_listener.is_default_listener for c_listener in self.listeners[listener.event]]
        removes_defaults = [c_listener.disable_default_listeners for c_listener in self.listeners[listener.event]]

        if any(default_listeners) and any(removes_defaults):
            self.listeners[listener.event] = [
                c_listener for c_listener in self.listeners[listener.event] if not c_listener.is_default_listener
            ]

    def add_interaction(self, command: InteractionCommand) -> bool:
        """
        Add a slash command to the client.

        Args:
            command InteractionCommand: The command to add

        """
        if self.debug_scope:
            command.scopes = [self.debug_scope]

        if self.disable_dm_commands:
            command.dm_permission = False

        # for SlashCommand objs without callback (like objects made to hold group info etc)
        if command.callback is None:
            return False

        if isinstance(command, SlashCommand):
            command._parse_parameters()

        base, group, sub, *_ = [*command.resolved_name.split(" "), None, None]

        for scope in command.scopes:
            if scope not in self.interactions_by_scope:
                self.interactions_by_scope[scope] = {}
            elif command.resolved_name in self.interactions_by_scope[scope]:
                old_cmd = self.interactions_by_scope[scope][command.resolved_name]
                raise ValueError(f"Duplicate Command! {scope}::{old_cmd.resolved_name}")

            # if self.enforce_interaction_perms:
            #     command.checks.append(command._permission_enforcer)

            self.interactions_by_scope[scope][command.resolved_name] = command

            if scope not in self.interaction_tree:
                self.interaction_tree[scope] = {}

            if group is None or isinstance(command, ContextMenu):
                self.interaction_tree[scope][command.resolved_name] = command
            else:
                if not (current := self.interaction_tree[scope].get(base)) or isinstance(current, SlashCommand):
                    self.interaction_tree[scope][base] = {}
                if sub is None:
                    self.interaction_tree[scope][base][group] = command
                else:
                    if not (current := self.interaction_tree[scope][base].get(group)) or isinstance(
                        current, SlashCommand
                    ):
                        self.interaction_tree[scope][base][group] = {}
                    self.interaction_tree[scope][base][group][sub] = command

        return True

    def add_component_callback(self, command: ComponentCommand) -> None:
        """
        Add a component callback to the client.

        Args:
            command: The command to add

        """
        for listener in command.listeners:
            if isinstance(listener, re.Pattern):
                if listener in self._regex_component_callbacks.keys():
                    raise ValueError(f"Duplicate Component! Multiple component callbacks for `{listener}`")
                self._regex_component_callbacks[listener] = command
            else:
                # I know this isn't an ideal solution, but it means we can lookup callbacks with O(1)
                if listener in self._component_callbacks.keys():
                    raise ValueError(f"Duplicate Component! Multiple component callbacks for `{listener}`")
                self._component_callbacks[listener] = command
            continue

    def add_modal_callback(self, command: ModalCommand) -> None:
        """
        Add a modal callback to the client.

        Args:
            command: The command to add

        """
        # test for parameters that arent the ctx (or self)
        if command.has_binding:
            callback = functools.partial(command.callback, None, None)
        else:
            callback = functools.partial(command.callback, None)

        if not inspect.signature(callback).parameters:
            # if there are none, notify the command to just pass the ctx and not kwargs
            # TODO: just make modal callbacks not pass kwargs at all (breaking)
            command._just_ctx = True

        for listener in command.listeners:
            if isinstance(listener, re.Pattern):
                if listener in self._regex_component_callbacks.keys():
                    raise ValueError(f"Duplicate Component! Multiple modal callbacks for `{listener}`")
                self._regex_modal_callbacks[listener] = command
            else:
                if listener in self._modal_callbacks.keys():
                    raise ValueError(f"Duplicate Component! Multiple modal callbacks for `{listener}`")
                self._modal_callbacks[listener] = command
            continue

    def add_global_autocomplete(self, callback: GlobalAutoComplete) -> None:
        """
        Add a global autocomplete to the client.

        Args:
            callback: The autocomplete to add

        """
        self._global_autocompletes[callback.option_name] = callback

    def add_command(self, func: Callable) -> None:
        """
        Add a command to the client.

        Args:
            func: The command to add

        """
        if isinstance(func, ModalCommand):
            self.add_modal_callback(func)
        elif isinstance(func, ComponentCommand):
            self.add_component_callback(func)
        elif isinstance(func, InteractionCommand):
            self.add_interaction(func)
        elif isinstance(func, Listener):
            self.add_listener(func)
        elif isinstance(func, GlobalAutoComplete):
            self.add_global_autocomplete(func)
        elif not isinstance(func, BaseCommand):
            raise TypeError("Invalid command type")

        for hook in self._add_command_hook:
            hook(func)

        if not func.callback:
            # for group = SlashCommand(...) usage
            return

        if isinstance(func.callback, functools.partial):
            ext = getattr(func, "extension", None)
            self.logger.debug(f"Added callback: {f'{ext.name}.' if ext else ''}{func.callback.func.__name__}")
        else:
            self.logger.debug(f"Added callback: {func.callback.__name__}")

        self.dispatch(CallbackAdded(callback=func, extension=func.extension if hasattr(func, "extension") else None))

    def _gather_callbacks(self) -> None:
        """Gathers callbacks from __main__ and self."""

        def process(callables, location: str) -> None:
            added = 0
            for func in callables:
                try:
                    self.add_command(func)
                    added += 1
                except TypeError:
                    self.logger.debug(f"Failed to add callback {func} from {location}")
                    continue

            self.logger.debug(f"{added} callbacks have been loaded from {location}.")

        main_commands = [
            obj for _, obj in inspect.getmembers(sys.modules["__main__"]) if isinstance(obj, CallbackObject)
        ]
        client_commands = [
            obj.copy_with_binding(self) for _, obj in inspect.getmembers(self) if isinstance(obj, CallbackObject)
        ]
        process(main_commands, "__main__")
        process(client_commands, self.__class__.__name__)

        [wrap_partial(obj, self) for _, obj in inspect.getmembers(self) if isinstance(obj, Task)]

    async def _init_interactions(self) -> None:
        """
        Initialise slash commands.

        If `sync_interactions` this will submit all registered slash
        commands to discord. Otherwise, it will get the list of
        interactions and cache their scopes.

        """
        # allow for ext and main to share the same decorator
        try:
            if self.sync_interactions:
                await self.synchronise_interactions()
            else:
                await self._cache_interactions(warn_missing=False)
        except Exception as e:
            self.dispatch(events.Error(source="Interaction Syncing", error=e))

    async def _cache_interactions(self, warn_missing: bool = False) -> None:
        """Get all interactions used by this bot and cache them."""
        if warn_missing or self.del_unused_app_cmd:
            bot_scopes = {g.id for g in self.cache.guild_cache.values()}
            bot_scopes.add(GLOBAL_SCOPE)
        else:
            bot_scopes = set(self.interactions_by_scope)

        sem = asyncio.Semaphore(5)

        async def wrap(*args, **kwargs) -> Absent[List[Dict]]:
            async with sem:
                try:
                    return await self.http.get_application_commands(*args, **kwargs)
                except Forbidden:
                    return MISSING

        results = await asyncio.gather(*[wrap(self.app.id, scope) for scope in bot_scopes])
        results = dict(zip(bot_scopes, results, strict=False))

        for scope, remote_cmds in results.items():
            if remote_cmds == MISSING:
                self.logger.debug(f"Bot was not invited to guild {scope} with `application.commands` scope")
                continue

            remote_cmds = {cmd_data["name"]: cmd_data for cmd_data in remote_cmds}

            found = set()
            if scope in self.interactions_by_scope:
                for cmd in self.interactions_by_scope[scope].values():
                    cmd_name = str(cmd.name)
                    cmd_data = remote_cmds.get(cmd_name, MISSING)
                    if cmd_data is MISSING:
                        if cmd_name not in found and warn_missing:
                            self.logger.error(
                                f'Detected yet to sync slash command "/{cmd_name}" for scope '
                                f'{"global" if scope == GLOBAL_SCOPE else scope}'
                            )
                        continue
                    found.add(cmd_name)
                    self.update_command_cache(scope, cmd.resolved_name, cmd_data["id"])

            if warn_missing:
                for cmd_data in remote_cmds.values():
                    self.logger.error(
                        f"Detected unimplemented slash command \"/{cmd_data['name']}\" for scope "
                        f"{'global' if scope == GLOBAL_SCOPE else scope}"
                    )

    async def synchronise_interactions(
        self,
        *,
        scopes: Sequence["Snowflake_Type"] = MISSING,
        delete_commands: Absent[bool] = MISSING,
    ) -> None:
        """
        Synchronise registered interactions with discord.

        Args:
            scopes: Optionally specify which scopes are to be synced.
            delete_commands: Override the client setting and delete commands.

        Returns:
            None

        Raises:
            InteractionMissingAccess: If bot is lacking the necessary access.
            Exception: If there is an error during the synchronization process.

        """
        s = time.perf_counter()
        _delete_cmds = self.del_unused_app_cmd if delete_commands is MISSING else delete_commands
        await self._cache_interactions()

        cmd_scopes = self._get_sync_scopes(scopes)
        local_cmds_json = application_commands_to_dict(self.interactions_by_scope, self)

        await asyncio.gather(*[self.sync_scope(scope, _delete_cmds, local_cmds_json) for scope in cmd_scopes])

        t = time.perf_counter() - s
        self.logger.debug(f"Sync of {len(cmd_scopes)} scopes took {t} seconds")

    def _get_sync_scopes(self, scopes: Sequence["Snowflake_Type"]) -> List["Snowflake_Type"]:
        """
        Determine which scopes to sync.

        Args:
            scopes: The scopes to sync.

        Returns:
            The scopes to sync.

        """
        if scopes is not MISSING:
            return scopes
        if self.del_unused_app_cmd:
            return [to_snowflake(g_id) for g_id in self._user._guild_ids] + [GLOBAL_SCOPE]
        return list(set(self.interactions_by_scope) | {GLOBAL_SCOPE})

    async def sync_scope(
        self,
        cmd_scope: "Snowflake_Type",
        delete_cmds: bool,
        local_cmds_json: Dict["Snowflake_Type", List[Dict[str, Any]]],
    ) -> None:
        """
        Sync a single scope.

        Args:
            cmd_scope: The scope to sync.
            delete_cmds: Whether to delete commands.
            local_cmds_json: The local commands in json format.

        """
        sync_needed_flag = False
        sync_payload = []

        try:
            remote_commands = await self.get_remote_commands(cmd_scope)
            sync_payload, sync_needed_flag = self._build_sync_payload(
                remote_commands, cmd_scope, local_cmds_json, delete_cmds
            )

            if sync_needed_flag or (delete_cmds and len(sync_payload) < len(remote_commands)):
                await self._sync_commands_with_discord(sync_payload, cmd_scope)
            else:
                self.logger.debug(f"{cmd_scope} is already up-to-date with {len(remote_commands)} commands.")

        except Forbidden as e:
            raise InteractionMissingAccess(cmd_scope) from e
        except HTTPException as e:
            self._raise_sync_exception(e, local_cmds_json, cmd_scope)

    async def get_remote_commands(self, cmd_scope: "Snowflake_Type") -> List[Dict[str, Any]]:
        """
        Get the remote commands for a scope.

        Args:
            cmd_scope: The scope to get the commands for.

        """
        try:
            return await self.http.get_application_commands(self.app.id, cmd_scope)
        except Forbidden:
            self.logger.warning(f"Bot is lacking `application.commands` scope in {cmd_scope}!")
            return []

    def _build_sync_payload(
        self,
        remote_commands: List[Dict[str, Any]],
        cmd_scope: "Snowflake_Type",
        local_cmds_json: Dict["Snowflake_Type", List[Dict[str, Any]]],
        delete_cmds: bool,
    ) -> Tuple[List[Dict[str, Any]], bool]:
        """
        Build the sync payload for a single scope.

        Args:
            remote_commands: The remote commands.
            cmd_scope: The scope to sync.
            local_cmds_json: The local commands in json format.
            delete_cmds: Whether to delete commands.

        """
        sync_payload = []
        sync_needed_flag = False

        for local_cmd in self.interactions_by_scope.get(cmd_scope, {}).values():
            remote_cmd_json = next(
                (c for c in remote_commands if int(c["id"]) == int(local_cmd.cmd_id.get(cmd_scope, 0))), None
            )
            local_cmd_json = next((c for c in local_cmds_json[cmd_scope] if c["name"] == str(local_cmd.name)))

            if sync_needed(local_cmd_json, remote_cmd_json):
                sync_needed_flag = True
                sync_payload.append(local_cmd_json)
            elif not delete_cmds and remote_cmd_json:
                _remote_payload = {
                    k: v for k, v in remote_cmd_json.items() if k not in ("id", "application_id", "version")
                }
                sync_payload.append(_remote_payload)
            elif delete_cmds:
                sync_payload.append(local_cmd_json)

        sync_payload = [FastJson.loads(_dump) for _dump in {FastJson.dumps(_cmd) for _cmd in sync_payload}]
        return sync_payload, sync_needed_flag

    async def _sync_commands_with_discord(
        self, sync_payload: List[Dict[str, Any]], cmd_scope: "Snowflake_Type"
    ) -> None:
        """
        Sync the commands with discord.

        Args:
            sync_payload: The sync payload.
            cmd_scope: The scope to sync.

        """
        self.logger.info(f"Overwriting {cmd_scope} with {len(sync_payload)} application commands")
        sync_response: list[dict] = await self.http.overwrite_application_commands(self.app.id, sync_payload, cmd_scope)
        self._cache_sync_response(sync_response, cmd_scope)

    def get_application_cmd_by_id(
        self, cmd_id: "Snowflake_Type", *, scope: "Snowflake_Type" = None
    ) -> Optional[InteractionCommand]:
        """
        Get a application command from the internal cache by its ID.

        Args:
            cmd_id: The ID of the command
            scope: Optionally specify a scope to search in

        Returns:
            The command, if one with the given ID exists internally, otherwise None

        """
        cmd_id = to_snowflake(cmd_id)
        scope = to_snowflake(scope) if scope is not None else None

        if scope is not None:
            return next(
                (cmd for cmd in self.interactions_by_scope[scope].values() if cmd.get_cmd_id(scope) == cmd_id), None
            )
        return next(cmd for cmd in self._interaction_lookup.values() if cmd_id in cmd.cmd_id.values())

    def _raise_sync_exception(self, e: HTTPException, cmds_json: dict, cmd_scope: "Snowflake_Type") -> NoReturn:
        try:
            if isinstance(e.errors, dict):
                for cmd_num in e.errors.keys():
                    cmd = cmds_json[cmd_scope][int(cmd_num)]
                    output = e.search_for_message(e.errors[cmd_num], cmd)
                    if len(output) > 1:
                        output = "\n".join(output)
                        self.logger.error(f"Multiple Errors found in command `{cmd['name']}`:\n{output}")
                    else:
                        self.logger.error(f"Error in command `{cmd['name']}`: {output[0]}")
            else:
                raise e from None
        except Exception:
            # the above shouldn't fail, but if it does, just raise the exception normally
            raise e from None

    def _cache_sync_response(self, sync_response: list[dict], scope: "Snowflake_Type") -> None:
        for cmd_data in sync_response:
            command_id = Snowflake(cmd_data["id"])
            tier_0_name = cmd_data["name"]
            options = cmd_data.get("options", [])

            if any(option["type"] in (OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP) for option in options):
                for option in options:
                    option_type = option["type"]

                    if option_type in (OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP):
                        tier_2_name = f"{tier_0_name} {option['name']}"

                        if option_type == OptionType.SUB_COMMAND_GROUP:
                            for sub_option in option.get("options", []):
                                tier_3_name = f"{tier_2_name} {sub_option['name']}"
                                self.update_command_cache(scope, tier_3_name, command_id)
                        else:
                            self.update_command_cache(scope, tier_2_name, command_id)

            else:
                self.update_command_cache(scope, tier_0_name, command_id)

    def update_command_cache(self, scope: "Snowflake_Type", command_name: str, command_id: "Snowflake") -> None:
        """
        Update the internal cache with a command ID.

        Args:
            scope: The scope of the command to update
            command_name: The name of the command
            command_id: The ID of the command

        """
        if command := self.interactions_by_scope[scope].get(command_name):
            command.cmd_id[scope] = command_id
            self._interaction_lookup[command.resolved_name] = command

    async def get_context(self, data: dict) -> InteractionContext[Self]:
        match data["type"]:
            case InteractionType.MESSAGE_COMPONENT:
                cls = self.component_context.from_dict(self, data)
            case InteractionType.AUTOCOMPLETE:
                cls = self.autocomplete_context.from_dict(self, data)
            case InteractionType.MODAL_RESPONSE:
                cls = self.modal_context.from_dict(self, data)
            case InteractionType.APPLICATION_COMMAND:
                if data["data"].get("target_id"):
                    cls = self.context_menu_context.from_dict(self, data)
                else:
                    cls = self.slash_context.from_dict(self, data)
            case _:
                self.logger.warning(f"Unknown interaction type [{data['type']}] - please update or report this.")
                cls = self.interaction_context.from_dict(self, data)
        if not cls.channel:
            # fallback channel if not provided
            try:
                if cls.guild_id:
                    channel = await self.cache.fetch_channel(data["channel_id"])
                else:
                    channel = await self.cache.fetch_dm_channel(cls.author_id)
                cls.channel_id = channel.id
            except Forbidden:
                self.logger.debug(f"Failed to fetch channel data for {data['channel_id']}")
        return cls

    async def handle_pre_ready_response(self, data: dict) -> None:
        """
        Respond to an interaction that was received before the bot was ready.

        Args:
            data: The interaction data

        """
        if data["type"] == InteractionType.AUTOCOMPLETE:
            # we do not want to respond to autocompletes as discord will cache the response,
            # so we just ignore them
            return

        with contextlib.suppress(HTTPException):
            await self.http.post_initial_response(
                {
                    "type": CallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
                    "data": {
                        "content": f"{self.user.display_name} is starting up. Please try again in a few seconds",
                        "flags": MessageFlags.EPHEMERAL,
                    },
                },
                token=data["token"],
                interaction_id=data["id"],
            )

    async def _run_slash_command(self, command: SlashCommand, ctx: "InteractionContext") -> Any:
        """Overrideable method that executes slash commands, can be used to wrap callback execution"""
        return await command(ctx, **ctx.kwargs)

    @processors.Processor.define("raw_interaction_create")
    async def _dispatch_interaction(self, event: RawGatewayEvent) -> None:  # noqa: C901
        """
        Identify and dispatch interaction of slash commands or components.

        Args:
            raw interaction event

        """
        interaction_data = event.data

        if not self._startup:
            self.logger.warning("Received interaction before startup completed, ignoring")
            if self.send_not_ready_messages:
                await self.handle_pre_ready_response(interaction_data)
            return

        if interaction_data["type"] in (
            InteractionType.APPLICATION_COMMAND,
            InteractionType.AUTOCOMPLETE,
        ):
            interaction_id = interaction_data["data"]["id"]
            name = interaction_data["data"]["name"]

            ctx = await self.get_context(interaction_data)
            if ctx.command:
                self.logger.debug(f"{ctx.command_id}::{ctx.command.name} should be called")

                if ctx.command.auto_defer:
                    auto_defer = ctx.command.auto_defer
                elif ctx.command.extension and ctx.command.extension.auto_defer:
                    auto_defer = ctx.command.extension.auto_defer
                else:
                    auto_defer = self.auto_defer

                if auto_opt := getattr(ctx, "focussed_option", None):
                    if autocomplete := ctx.command.autocomplete_callbacks.get(str(auto_opt.name)):
                        if ctx.command.has_binding:
                            callback = functools.partial(ctx.command.call_with_binding, autocomplete)
                        else:
                            callback = autocomplete
                    elif autocomplete := self._global_autocompletes.get(str(auto_opt.name)):
                        callback = autocomplete
                    else:
                        raise ValueError(f"Autocomplete callback for {auto_opt.name!s} not found")

                    await self.__dispatch_interaction(
                        ctx=ctx,
                        callback=callback(ctx),
                        callback_kwargs=ctx.kwargs,
                        error_callback=events.AutocompleteError,
                        completion_callback=events.AutocompleteCompletion,
                    )
                else:
                    await auto_defer(ctx)
                    await self.__dispatch_interaction(
                        ctx=ctx,
                        callback=self._run_slash_command(ctx.command, ctx),
                        callback_kwargs=ctx.kwargs,
                        error_callback=events.CommandError,
                        completion_callback=events.CommandCompletion,
                    )
            else:
                self.logger.error(f"Unknown cmd_id received:: {interaction_id} ({name})")

        elif interaction_data["type"] == InteractionType.MESSAGE_COMPONENT:
            # Buttons, Selects, ContextMenu::Message
            ctx = await self.get_context(interaction_data)
            component_type = interaction_data["data"]["component_type"]

            self.dispatch(events.Component(ctx=ctx))
            component_callback = self._component_callbacks.get(ctx.custom_id)
            if not component_callback:
                # evaluate regex component callbacks
                for regex, callback in self._regex_component_callbacks.items():
                    if regex.match(ctx.custom_id):
                        component_callback = callback
                        break

            if component_callback:
                await self.__dispatch_interaction(
                    ctx=ctx,
                    callback=component_callback(ctx),
                    error_callback=events.ComponentError,
                    completion_callback=events.ComponentCompletion,
                )

            if component_type == ComponentType.BUTTON:
                self.dispatch(events.ButtonPressed(ctx))

            if component_type == ComponentType.STRING_SELECT:
                self.dispatch(events.Select(ctx))

        elif interaction_data["type"] == InteractionType.MODAL_RESPONSE:
            ctx = await self.get_context(interaction_data)
            self.dispatch(events.ModalCompletion(ctx=ctx))

            modal_callback = self._modal_callbacks.get(ctx.custom_id)
            if not modal_callback:
                # evaluate regex component callbacks
                for regex, callback in self._regex_modal_callbacks.items():
                    if regex.match(ctx.custom_id):
                        modal_callback = callback
                        break

            if modal_callback:
                await self.__dispatch_interaction(
                    ctx=ctx, callback=modal_callback(ctx), error_callback=events.ModalError
                )

        else:
            raise NotImplementedError(f"Unknown Interaction Received: {interaction_data['type']}")

    # todo add typing once context is re-implemented
    async def __dispatch_interaction(
        self,
        ctx,
        callback: Coroutine,
        error_callback: Type[BaseEvent],
        completion_callback: Type[BaseEvent] | None = None,
        callback_kwargs: dict | None = None,
    ) -> None:
        if callback_kwargs is None:
            callback_kwargs = {}

        try:
            if self.pre_run_callback:
                await self.pre_run_callback(ctx, **callback_kwargs)

            # allow interactions to be responded by returning a string or an embed
            response = await callback
            if not getattr(ctx, "responded", True) and response:
                if isinstance(response, Embed) or (
                    isinstance(response, list) and all(isinstance(item, Embed) for item in response)
                ):
                    await ctx.send(embeds=response)
                else:
                    if not isinstance(response, str):
                        self.logger.warning(
                            "Command callback returned non-string value - casting to string and sending"
                        )
                    await ctx.send(str(response))

            if self.post_run_callback:
                _ = asyncio.create_task(self.post_run_callback(ctx, **callback_kwargs))  # noqa: RUF006
        except Exception as e:
            self.dispatch(error_callback(ctx=ctx, error=e))
        finally:
            if completion_callback:
                self.dispatch(completion_callback(ctx=ctx))

    @Listener.create("disconnect", is_default_listener=True)
    async def _disconnect(self) -> None:
        self._ready.clear()

    def get_extensions(self, name: str) -> list[Extension]:
        """
        Get all ext with a name or extension name.

        Args:
            name: The name of the extension, or the name of it's extension

        Returns:
            List of Extensions

        """
        if name not in self.ext.keys():
            return [ext for ext in self.ext.values() if ext.extension_name == name]

        return [self.ext.get(name, None)]

    def get_ext(self, name: str) -> Extension | None:
        """
        Get a extension with a name or extension name.

        Args:
            name: The name of the extension, or the name of it's extension

        Returns:
            A extension, if found

        """
        return ext[0] if (ext := self.get_extensions(name)) else None

    def __load_module(self, module, module_name, **load_kwargs) -> None:
        """Internal method that handles loading a module."""
        try:
            if setup := getattr(module, "setup", None):
                setup(self, **load_kwargs)
            else:
                self.logger.debug("No setup function found in %s", module_name)

                found = False
                objects = {name: obj for name, obj in inspect.getmembers(module) if isinstance(obj, type)}
                for obj_name, obj in objects.items():
                    if Extension in obj.__bases__:
                        self.logger.debug(f"Found extension class {obj_name} in {module_name}: Attempting to load")
                        obj(self, **load_kwargs)
                        found = True
                if not found:
                    raise ValueError(f"{module_name} contains no Extensions")

        except ExtensionLoadException:
            raise
        except Exception as e:
            sys.modules.pop(module_name, None)
            raise ExtensionLoadException(f"Unexpected Error loading {module_name}") from e

        else:
            self.logger.debug(f"Loaded Extension: {module_name}")
            self.__modules[module_name] = module

            if self.sync_ext and self._ready.is_set():
                try:
                    asyncio.get_running_loop()
                except RuntimeError:
                    return
                _ = asyncio.create_task(self.synchronise_interactions())  # noqa: RUF006

    def load_extension(
        self,
        name: str,
        package: str | None = None,
        **load_kwargs: Any,
    ) -> None:
        """
        Load an extension with given arguments.

        Args:
            name: The name of the extension.
            package: The package the extension is in
            **load_kwargs: The auto-filled mapping of the load keyword arguments

        """
        module_name = importlib.util.resolve_name(name, package)
        if module_name in self.__modules:
            raise Exception(f"{module_name} already loaded")

        module = importlib.import_module(module_name, package)
        self.__load_module(module, module_name, **load_kwargs)

    def load_extensions(
        self,
        *packages: str,
        recursive: bool = False,
        **load_kwargs: Any,
    ) -> None:
        """
        Load multiple extensions at once.

        Removes the need of manually looping through the package
        and loading the extensions.

        Args:
            *packages: The package(s) where the extensions are located.
            recursive: Whether to load extensions from the subdirectories within the package.

        """
        if not packages:
            raise ValueError("You must specify at least one package.")

        for package in packages:
            # If recursive then include subdirectories ('**')
            # otherwise just the package specified by the user.
            pattern = os.path.join(package, "**" if recursive else "", "*.py")

            # Find all files matching the pattern, and convert slashes to dots.
            extensions = [f.replace(os.path.sep, ".").replace(".py", "") for f in glob.glob(pattern, recursive=True)]

            for ext in extensions:
                self.load_extension(ext, **load_kwargs)

    def unload_extension(
        self, name: str, package: str | None = None, force: bool = False, **unload_kwargs: Any
    ) -> None:
        """
        Unload an extension with given arguments.

        Args:
            name: The name of the extension.
            package: The package the extension is in
            force: Whether to force unload the extension - for use in reversions
            **unload_kwargs: The auto-filled mapping of the unload keyword arguments

        """
        name = importlib.util.resolve_name(name, package)
        module = self.__modules.get(name)

        if module is None and not force:
            raise ExtensionNotFound(f"No extension called {name} is loaded")

        with contextlib.suppress(AttributeError):
            teardown = module.teardown
            teardown(**unload_kwargs)

        for ext in self.get_extensions(name):
            ext.drop(**unload_kwargs)

        sys.modules.pop(name, None)
        self.__modules.pop(name, None)

        if self.sync_ext and self._ready.is_set():
            try:
                asyncio.get_running_loop()
            except RuntimeError:
                return
            _ = asyncio.create_task(self.synchronise_interactions())  # noqa: RUF006

    def reload_extension(
        self,
        name: str,
        package: str | None = None,
        *,
        load_kwargs: Any = None,
        unload_kwargs: Any = None,
    ) -> None:
        """
        Helper method to reload an extension. Simply unloads, then loads the extension with given arguments.

        Args:
            name: The name of the extension.
            package: The package the extension is in
            load_kwargs: The manually-filled mapping of the load keyword arguments
            unload_kwargs: The manually-filled mapping of the unload keyword arguments

        """
        name = importlib.util.resolve_name(name, package)
        module = self.__modules.get(name)

        if module is None:
            self.logger.warning("Attempted to reload extension thats not loaded. Loading extension instead")
            return self.load_extension(name, package)

        backup = module

        try:
            if not load_kwargs:
                load_kwargs = {}
            if not unload_kwargs:
                unload_kwargs = {}

            self.unload_extension(name, package, **unload_kwargs)
            self.load_extension(name, package, **load_kwargs)
        except Exception as e:
            try:
                self.logger.error(f"Error reloading extension {name}: {e} - attempting to revert to previous state")
                try:
                    self.unload_extension(name, package, force=True, **unload_kwargs)  # make sure no remnants are left
                except Exception as t:
                    self.logger.debug(f"Suppressing error unloading extension {name} during reload revert: {t}")

                sys.modules[name] = backup
                self.__load_module(backup, name, **load_kwargs)
                self.logger.info(f"Reverted extension {name} to previous state ", exc_info=e)
            except Exception as ex:
                sys.modules.pop(name, None)
                raise ex from e

    async def fetch_guild(self, guild_id: "Snowflake_Type", *, force: bool = False) -> Optional[Guild]:
        """
        Fetch a guild.

        !!! note
            This method is an alias for the cache which will either return a cached object, or query discord for the object
            if its not already cached.

        Args:
            guild_id: The ID of the guild to get
            force: Whether to poll the API regardless of cache

        Returns:
            Guild Object if found, otherwise None

        """
        try:
            return await self.cache.fetch_guild(guild_id, force=force)
        except NotFound:
            return None

    def get_guild(self, guild_id: "Snowflake_Type") -> Optional[Guild]:
        """
        Get a guild.

        !!! note
            This method is an alias for the cache which will return a cached object.

        Args:
            guild_id: The ID of the guild to get

        Returns:
            Guild Object if found, otherwise None

        """
        return self.cache.get_guild(guild_id)

    async def create_guild_from_template(
        self,
        template_code: Union["GuildTemplate", str],
        name: str,
        icon: Absent[UPLOADABLE_TYPE] = MISSING,
    ) -> Optional[Guild]:
        """
        Creates a new guild based on a template.

        !!! note
            This endpoint can only be used by bots in less than 10 guilds.

        Args:
            template_code: The code of the template to use.
            name: The name of the guild (2-100 characters)
            icon: Location or File of icon to set

        Returns:
            The newly created guild object

        """
        if isinstance(template_code, GuildTemplate):
            template_code = template_code.code

        if icon:
            icon = to_image_data(icon)
        guild_data = await self.http.create_guild_from_guild_template(template_code, name, icon)
        return Guild.from_dict(guild_data, self)

    async def fetch_channel(self, channel_id: "Snowflake_Type", *, force: bool = False) -> Optional["TYPE_ALL_CHANNEL"]:
        """
        Fetch a channel.

        !!! note
            This method is an alias for the cache which will either return a cached object, or query discord for the object
            if its not already cached.

        Args:
            channel_id: The ID of the channel to get
            force: Whether to poll the API regardless of cache

        Returns:
            Channel Object if found, otherwise None

        """
        try:
            return await self.cache.fetch_channel(channel_id, force=force)
        except NotFound:
            return None

    def get_channel(self, channel_id: "Snowflake_Type") -> Optional["TYPE_ALL_CHANNEL"]:
        """
        Get a channel.

        !!! note
            This method is an alias for the cache which will return a cached object.

        Args:
            channel_id: The ID of the channel to get

        Returns:
            Channel Object if found, otherwise None

        """
        return self.cache.get_channel(channel_id)

    async def fetch_user(self, user_id: "Snowflake_Type", *, force: bool = False) -> Optional[User]:
        """
        Fetch a user.

        !!! note
            This method is an alias for the cache which will either return a cached object, or query discord for the object
            if its not already cached.

        Args:
            user_id: The ID of the user to get
            force: Whether to poll the API regardless of cache

        Returns:
            User Object if found, otherwise None

        """
        try:
            return await self.cache.fetch_user(user_id, force=force)
        except NotFound:
            return None

    def get_user(self, user_id: "Snowflake_Type") -> Optional[User]:
        """
        Get a user.

        !!! note
            This method is an alias for the cache which will return a cached object.

        Args:
            user_id: The ID of the user to get

        Returns:
            User Object if found, otherwise None

        """
        return self.cache.get_user(user_id)

    async def fetch_member(
        self, user_id: "Snowflake_Type", guild_id: "Snowflake_Type", *, force: bool = False
    ) -> Optional[Member]:
        """
        Fetch a member from a guild.

        !!! note
            This method is an alias for the cache which will either return a cached object, or query discord for the object
            if its not already cached.

        Args:
            user_id: The ID of the member
            guild_id: The ID of the guild to get the member from
            force: Whether to poll the API regardless of cache

        Returns:
            Member object if found, otherwise None

        """
        try:
            return await self.cache.fetch_member(guild_id, user_id, force=force)
        except NotFound:
            return None

    def get_member(self, user_id: "Snowflake_Type", guild_id: "Snowflake_Type") -> Optional[Member]:
        """
        Get a member from a guild.

        !!! note
            This method is an alias for the cache which will return a cached object.

        Args:
            user_id: The ID of the member
            guild_id: The ID of the guild to get the member from

        Returns:
            Member object if found, otherwise None

        """
        return self.cache.get_member(guild_id, user_id)

    async def fetch_scheduled_event(
        self,
        guild_id: "Snowflake_Type",
        scheduled_event_id: "Snowflake_Type",
        with_user_count: bool = False,
    ) -> Optional["ScheduledEvent"]:
        """
        Fetch a scheduled event by id.

        Args:
            guild_id: The ID of the guild to get the scheduled event from
            scheduled_event_id: The ID of the scheduled event to get
            with_user_count: Whether to include the user count in the response

        Returns:
            The scheduled event if found, otherwise None

        """
        try:
            scheduled_event_data = await self.http.get_scheduled_event(guild_id, scheduled_event_id, with_user_count)
            return self.cache.place_scheduled_event_data(scheduled_event_data)
        except NotFound:
            return None

    def get_scheduled_event(
        self,
        scheduled_event_id: "Snowflake_Type",
    ) -> Optional["ScheduledEvent"]:
        """
        Get a scheduled event by id.

        !!! note
            This method is an alias for the cache which will return a cached object.

        Args:
            scheduled_event_id: The ID of the scheduled event to get

        Returns:
            The scheduled event if found, otherwise None

        """
        return self.cache.get_scheduled_event(scheduled_event_id)

    async def fetch_custom_emoji(
        self, emoji_id: "Snowflake_Type", guild_id: "Snowflake_Type", *, force: bool = False
    ) -> Optional[CustomEmoji]:
        """
        Fetch a custom emoji by id.

        Args:
            emoji_id: The id of the custom emoji.
            guild_id: The id of the guild the emoji belongs to.
            force: Whether to poll the API regardless of cache.

        Returns:
            The custom emoji if found, otherwise None.

        """
        try:
            return await self.cache.fetch_emoji(guild_id, emoji_id, force=force)
        except NotFound:
            return None

    def get_custom_emoji(
        self, emoji_id: "Snowflake_Type", guild_id: Optional["Snowflake_Type"] = None
    ) -> Optional[CustomEmoji]:
        """
        Get a custom emoji by id.

        Args:
            emoji_id: The id of the custom emoji.
            guild_id: The id of the guild the emoji belongs to.

        Returns:
            The custom emoji if found, otherwise None.

        """
        emoji = self.cache.get_emoji(emoji_id)
        if emoji and (not guild_id or emoji._guild_id == to_snowflake(guild_id)):
            return emoji
        return None

    async def fetch_sticker(self, sticker_id: "Snowflake_Type") -> Optional[Sticker]:
        """
        Fetch a sticker by ID.

        Args:
            sticker_id: The ID of the sticker.

        Returns:
            A sticker object if found, otherwise None

        """
        try:
            sticker_data = await self.http.get_sticker(sticker_id)
            return Sticker.from_dict(sticker_data, self)
        except NotFound:
            return None

    async def fetch_nitro_packs(self) -> Optional[List["StickerPack"]]:
        """
        List the sticker packs available to Nitro subscribers.

        Returns:
            A list of StickerPack objects if found, otherwise returns None

        """
        try:
            packs_data = await self.http.list_nitro_sticker_packs()
            return [StickerPack.from_dict(data, self) for data in packs_data]

        except NotFound:
            return None

    async def fetch_voice_regions(self) -> List["VoiceRegion"]:
        """
        List the voice regions available on Discord.

        Returns:
            A list of voice regions.

        """
        regions_data = await self.http.list_voice_regions()
        return VoiceRegion.from_list(regions_data)

    async def connect_to_vc(
        self,
        guild_id: "Snowflake_Type",
        channel_id: "Snowflake_Type",
        muted: bool = False,
        deafened: bool = False,
    ) -> ActiveVoiceState:
        """
        Connect the bot to a voice channel.

        Args:
            guild_id: id of the guild the voice channel is in.
            channel_id: id of the voice channel client wants to join.
            muted: Whether the bot should be muted when connected.
            deafened: Whether the bot should be deafened when connected.

        Returns:
            The new active voice state on successfully connection.

        """
        return await self._connection_state.voice_connect(guild_id, channel_id, muted, deafened)

    def get_bot_voice_state(self, guild_id: "Snowflake_Type") -> Optional[ActiveVoiceState]:
        """
        Get the bot's voice state for a guild.

        Args:
            guild_id: The target guild's id.

        Returns:
            The bot's voice state for the guild if connected, otherwise None.

        """
        return self._connection_state.get_voice_state(guild_id)

    async def fetch_entitlements(
        self,
        *,
        user_id: "Optional[Snowflake_Type]" = None,
        sku_ids: "Optional[list[Snowflake_Type]]" = None,
        before: "Optional[Snowflake_Type]" = None,
        after: "Optional[Snowflake_Type]" = None,
        limit: Optional[int] = 100,
        guild_id: "Optional[Snowflake_Type]" = None,
        exclude_ended: Optional[bool] = None,
    ) -> List[Entitlement]:
        """
        Fetch the entitlements for the bot's application.

        Args:
            user_id: The ID of the user to filter entitlements by.
            sku_ids: The IDs of the SKUs to filter entitlements by.
            before: Get entitlements before this ID.
            after: Get entitlements after this ID.
            limit: The maximum number of entitlements to return. Maximum is 100.
            guild_id: The ID of the guild to filter entitlements by.
            exclude_ended: Whether to exclude ended entitlements.

        Returns:
            A list of entitlements.

        """
        entitlements_data = await self.http.get_entitlements(
            self.app.id,
            user_id=user_id,
            sku_ids=sku_ids,
            before=before,
            after=after,
            limit=limit,
            guild_id=guild_id,
            exclude_ended=exclude_ended,
        )
        return Entitlement.from_list(entitlements_data, self)

    async def create_test_entitlement(
        self, sku_id: "Snowflake_Type", owner_id: "Snowflake_Type", owner_type: int
    ) -> Entitlement:
        """
        Create a test entitlement for the bot's application.

        Args:
            sku_id: The ID of the SKU to create the entitlement for.
            owner_id: The ID of the owner of the entitlement.
            owner_type: The type of the owner of the entitlement. 1 for a guild subscription, 2 for a user subscription

        Returns:
            The created entitlement.

        """
        payload = {"sku_id": to_snowflake(sku_id), "owner_id": to_snowflake(owner_id), "owner_type": owner_type}

        entitlement_data = await self.http.create_test_entitlement(payload, self.app.id)
        return Entitlement.from_dict(entitlement_data, self)

    async def delete_test_entitlement(self, entitlement_id: "Snowflake_Type") -> None:
        """
        Delete a test entitlement for the bot's application.

        Args:
            entitlement_id: The ID of the entitlement to delete.

        """
        await self.http.delete_test_entitlement(self.app.id, to_snowflake(entitlement_id))

    async def consume_entitlement(self, entitlement_id: "Snowflake_Type") -> None:
        """
        For One-Time Purchase consumable SKUs, marks a given entitlement for the user as consumed.

        Args:
            entitlement_id: The ID of the entitlement to consume.

        """
        await self.http.consume_entitlement(self.app.id, entitlement_id)

    def mention_command(self, name: str, scope: int = 0) -> str:
        """
        Returns a string that would mention the interaction specified.

        Args:
            name: The name of the interaction.
            scope: The scope of the interaction. Defaults to 0, the global scope.

        Returns:
            str: The interaction's mention in the specified scope.

        """
        return self.interactions_by_scope[scope][name].mention(scope)

    async def change_presence(
        self,
        status: Optional[Union[str, Status]] = Status.ONLINE,
        activity: Optional[Union[Activity, str]] = None,
    ) -> None:
        """
        Change the bots presence.

        Args:
            status: The status for the bot to be. i.e. online, afk, etc.
            activity: The activity for the bot to be displayed as doing.

        !!! note
            Bots may only be `playing` `streaming` `listening` `watching`  `competing` or `custom`

        """
        await self._connection_state.change_presence(status, activity)

activity: Activity property

Get the activity of the bot.

app: Application property

Returns the bots application.

application_commands: List[InteractionCommand] property

A list of all application commands registered within the bot.

async_startup_tasks: list[tuple[Callable[..., Coroutine], Iterable[Any], dict[str, Any]]] = [] instance-attribute

A list of coroutines to run during startup

auto_defer = auto_defer instance-attribute

A system to automatically defer commands after a set duration

autocomplete_context: Type[BaseContext[Self]] = autocomplete_context instance-attribute

The object to instantiate for Autocomplete Context

average_latency: float property

Returns the average latency of the websocket connection (seconds).

component_context: Type[BaseContext[Self]] = component_context instance-attribute

The object to instantiate for Component Context

context_menu_context: Type[BaseContext[Self]] = context_menu_context instance-attribute

The object to instantiate for Context Menu Context

debug_scope = to_snowflake(debug_scope) if debug_scope is not MISSING else MISSING instance-attribute

Sync global commands as guild for quicker command updates during debug

del_unused_app_cmd: bool = delete_unused_application_cmds instance-attribute

Should unused application commands be deleted?

ext: Dict[str, Extension] = {} instance-attribute

A dictionary of mounted ext

fetch_members = fetch_members instance-attribute

Fetch the full members list of all guilds on startup

gateway_started: bool property

Returns if the gateway has been started.

guild_event_timeout = 3 instance-attribute

How long to wait for guilds to be cached

guilds: List[Guild] property

Returns a list of all guilds the bot is in.

http: HTTPClient = HTTPClient(logger=self.logger, show_ratelimit_tracebacks=show_ratelimit_tracebacks, proxy=proxy) instance-attribute

The HTTP client to use when interacting with discord endpoints

interaction_context: Type[BaseContext[Self]] = interaction_context instance-attribute

The object to instantiate for Interaction Context

interaction_tree: Dict[Snowflake_Type, Dict[str, InteractionCommand | Dict[str, InteractionCommand]]] = {} instance-attribute

A dictionary of registered application commands in a tree

interactions_by_scope: Dict[Snowflake_Type, Dict[str, InteractionCommand]] = {} instance-attribute

A dictionary of registered application commands: {scope: [commands]}

is_closed: bool property

Returns True if the bot has closed.

is_ready: bool property

Returns True if the bot is ready.

latency: float property

Returns the latency of the websocket connection (seconds).

logger = logger instance-attribute

The logger interactions.py should use. Do not use in combination with Client.basic_logging and Client.logging_level.

Note

Different loggers with multiple clients are not supported

modal_context: Type[BaseContext[Self]] = modal_context instance-attribute

The object to instantiate for Modal Context

owner: Optional[User] property

Returns the bot's owner'.

owners: List[User] property

Returns the bot's owners as declared via client.owner_ids.

send_command_tracebacks: bool = send_command_tracebacks instance-attribute

Should the traceback of command errors be sent in reply to the command invocation

send_not_ready_messages: bool = send_not_ready_messages instance-attribute

Should the bot send a message when it is not ready yet in response to a command invocation

slash_context: Type[BaseContext[Self]] = slash_context instance-attribute

The object to instantiate for Slash Context

start_time: datetime property

The start time of the bot.

status: Status property

Get the status of the bot.

IE online, afk, dnd

sync_ext: bool = sync_ext instance-attribute

Should we sync whenever a extension is (un)loaded

sync_interactions: bool = sync_interactions instance-attribute

Should application commands be synced

user: ClientUser property

Returns the bot's user.

ws: GatewayClient property

Returns the websocket client.

__load_module(module, module_name, **load_kwargs)

Internal method that handles loading a module.

Source code in interactions/client/client.py
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
def __load_module(self, module, module_name, **load_kwargs) -> None:
    """Internal method that handles loading a module."""
    try:
        if setup := getattr(module, "setup", None):
            setup(self, **load_kwargs)
        else:
            self.logger.debug("No setup function found in %s", module_name)

            found = False
            objects = {name: obj for name, obj in inspect.getmembers(module) if isinstance(obj, type)}
            for obj_name, obj in objects.items():
                if Extension in obj.__bases__:
                    self.logger.debug(f"Found extension class {obj_name} in {module_name}: Attempting to load")
                    obj(self, **load_kwargs)
                    found = True
            if not found:
                raise ValueError(f"{module_name} contains no Extensions")

    except ExtensionLoadException:
        raise
    except Exception as e:
        sys.modules.pop(module_name, None)
        raise ExtensionLoadException(f"Unexpected Error loading {module_name}") from e

    else:
        self.logger.debug(f"Loaded Extension: {module_name}")
        self.__modules[module_name] = module

        if self.sync_ext and self._ready.is_set():
            try:
                asyncio.get_running_loop()
            except RuntimeError:
                return
            _ = asyncio.create_task(self.synchronise_interactions())  # noqa: RUF006

add_command(func)

Add a command to the client.

Parameters:

Name Type Description Default
func Callable

The command to add

required
Source code in interactions/client/client.py
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
def add_command(self, func: Callable) -> None:
    """
    Add a command to the client.

    Args:
        func: The command to add

    """
    if isinstance(func, ModalCommand):
        self.add_modal_callback(func)
    elif isinstance(func, ComponentCommand):
        self.add_component_callback(func)
    elif isinstance(func, InteractionCommand):
        self.add_interaction(func)
    elif isinstance(func, Listener):
        self.add_listener(func)
    elif isinstance(func, GlobalAutoComplete):
        self.add_global_autocomplete(func)
    elif not isinstance(func, BaseCommand):
        raise TypeError("Invalid command type")

    for hook in self._add_command_hook:
        hook(func)

    if not func.callback:
        # for group = SlashCommand(...) usage
        return

    if isinstance(func.callback, functools.partial):
        ext = getattr(func, "extension", None)
        self.logger.debug(f"Added callback: {f'{ext.name}.' if ext else ''}{func.callback.func.__name__}")
    else:
        self.logger.debug(f"Added callback: {func.callback.__name__}")

    self.dispatch(CallbackAdded(callback=func, extension=func.extension if hasattr(func, "extension") else None))

add_component_callback(command)

Add a component callback to the client.

Parameters:

Name Type Description Default
command ComponentCommand

The command to add

required
Source code in interactions/client/client.py
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
def add_component_callback(self, command: ComponentCommand) -> None:
    """
    Add a component callback to the client.

    Args:
        command: The command to add

    """
    for listener in command.listeners:
        if isinstance(listener, re.Pattern):
            if listener in self._regex_component_callbacks.keys():
                raise ValueError(f"Duplicate Component! Multiple component callbacks for `{listener}`")
            self._regex_component_callbacks[listener] = command
        else:
            # I know this isn't an ideal solution, but it means we can lookup callbacks with O(1)
            if listener in self._component_callbacks.keys():
                raise ValueError(f"Duplicate Component! Multiple component callbacks for `{listener}`")
            self._component_callbacks[listener] = command
        continue

add_event_processor(event_name=MISSING)

A decorator to be used to add event processors.

Parameters:

Name Type Description Default
event_name Absent[str]

The event name to use, if not the coroutine name

MISSING

Returns:

Type Description
Callable[[AsyncCallable], AsyncCallable]

A function that can be used to hook into the event.

Source code in interactions/client/client.py
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
def add_event_processor(self, event_name: Absent[str] = MISSING) -> Callable[[AsyncCallable], AsyncCallable]:
    """
    A decorator to be used to add event processors.

    Args:
        event_name: The event name to use, if not the coroutine name

    Returns:
        A function that can be used to hook into the event.

    """

    def wrapper(coro: AsyncCallable) -> AsyncCallable:
        name = event_name
        if name is MISSING:
            name = coro.__name__
        name = name.lstrip("_")
        name = name.removeprefix("on_")
        self.processors[name] = coro
        return coro

    return wrapper

add_global_autocomplete(callback)

Add a global autocomplete to the client.

Parameters:

Name Type Description Default
callback GlobalAutoComplete

The autocomplete to add

required
Source code in interactions/client/client.py
1461
1462
1463
1464
1465
1466
1467
1468
1469
def add_global_autocomplete(self, callback: GlobalAutoComplete) -> None:
    """
    Add a global autocomplete to the client.

    Args:
        callback: The autocomplete to add

    """
    self._global_autocompletes[callback.option_name] = callback

add_interaction(command)

Add a slash command to the client.

Parameters:

Name Type Description Default
command InteractionCommand

The command to add

required
Source code in interactions/client/client.py
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
def add_interaction(self, command: InteractionCommand) -> bool:
    """
    Add a slash command to the client.

    Args:
        command InteractionCommand: The command to add

    """
    if self.debug_scope:
        command.scopes = [self.debug_scope]

    if self.disable_dm_commands:
        command.dm_permission = False

    # for SlashCommand objs without callback (like objects made to hold group info etc)
    if command.callback is None:
        return False

    if isinstance(command, SlashCommand):
        command._parse_parameters()

    base, group, sub, *_ = [*command.resolved_name.split(" "), None, None]

    for scope in command.scopes:
        if scope not in self.interactions_by_scope:
            self.interactions_by_scope[scope] = {}
        elif command.resolved_name in self.interactions_by_scope[scope]:
            old_cmd = self.interactions_by_scope[scope][command.resolved_name]
            raise ValueError(f"Duplicate Command! {scope}::{old_cmd.resolved_name}")

        # if self.enforce_interaction_perms:
        #     command.checks.append(command._permission_enforcer)

        self.interactions_by_scope[scope][command.resolved_name] = command

        if scope not in self.interaction_tree:
            self.interaction_tree[scope] = {}

        if group is None or isinstance(command, ContextMenu):
            self.interaction_tree[scope][command.resolved_name] = command
        else:
            if not (current := self.interaction_tree[scope].get(base)) or isinstance(current, SlashCommand):
                self.interaction_tree[scope][base] = {}
            if sub is None:
                self.interaction_tree[scope][base][group] = command
            else:
                if not (current := self.interaction_tree[scope][base].get(group)) or isinstance(
                    current, SlashCommand
                ):
                    self.interaction_tree[scope][base][group] = {}
                self.interaction_tree[scope][base][group][sub] = command

    return True

add_listener(listener)

Add a listener for an event, if no event is passed, one is determined.

Parameters:

Name Type Description Default
listener Listener

The listener to add to the client

required
Source code in interactions/client/client.py
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
def add_listener(self, listener: Listener) -> None:
    """
    Add a listener for an event, if no event is passed, one is determined.

    Args:
        listener Listener: The listener to add to the client

    """
    if listener.event == "event":
        self.logger.critical(
            f"Subscribing to `{listener.event}` - Meta Events are very expensive; remember to remove it before"
            " releasing your bot"
        )

    if not listener.is_default_listener:
        # check that the required intents are enabled

        event_class_name = "".join([name.capitalize() for name in listener.event.split("_")])
        if event_class := globals().get(event_class_name):
            if required_intents := _INTENT_EVENTS.get(event_class):
                if all(required_intent not in self.intents for required_intent in required_intents):
                    self.logger.warning(
                        f"Event `{listener.event}` will not work since the required intent is not set -> Requires"
                        f" any of: `{required_intents}`"
                    )

    # prevent the same callback being added twice
    if listener in self.listeners.get(listener.event, []):
        self.logger.debug(f"Listener {listener} has already been hooked, not re-hooking it again")
        return

    listener.lazy_parse_params()

    if listener.event not in self.listeners:
        self.listeners[listener.event] = []
    self.listeners[listener.event].append(listener)

    # check if other listeners are to be deleted
    default_listeners = [c_listener.is_default_listener for c_listener in self.listeners[listener.event]]
    removes_defaults = [c_listener.disable_default_listeners for c_listener in self.listeners[listener.event]]

    if any(default_listeners) and any(removes_defaults):
        self.listeners[listener.event] = [
            c_listener for c_listener in self.listeners[listener.event] if not c_listener.is_default_listener
        ]

add_modal_callback(command)

Add a modal callback to the client.

Parameters:

Name Type Description Default
command ModalCommand

The command to add

required
Source code in interactions/client/client.py
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
def add_modal_callback(self, command: ModalCommand) -> None:
    """
    Add a modal callback to the client.

    Args:
        command: The command to add

    """
    # test for parameters that arent the ctx (or self)
    if command.has_binding:
        callback = functools.partial(command.callback, None, None)
    else:
        callback = functools.partial(command.callback, None)

    if not inspect.signature(callback).parameters:
        # if there are none, notify the command to just pass the ctx and not kwargs
        # TODO: just make modal callbacks not pass kwargs at all (breaking)
        command._just_ctx = True

    for listener in command.listeners:
        if isinstance(listener, re.Pattern):
            if listener in self._regex_component_callbacks.keys():
                raise ValueError(f"Duplicate Component! Multiple modal callbacks for `{listener}`")
            self._regex_modal_callbacks[listener] = command
        else:
            if listener in self._modal_callbacks.keys():
                raise ValueError(f"Duplicate Component! Multiple modal callbacks for `{listener}`")
            self._modal_callbacks[listener] = command
        continue

astart(token=None) async

Asynchronous method to start the bot.

Parameters:

Name Type Description Default
token str | None

Your bot's token

None
Source code in interactions/client/client.py
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
async def astart(self, token: str | None = None) -> None:
    """
    Asynchronous method to start the bot.

    Args:
        token: Your bot's token

    """
    await self.login(token)

    # run any pending startup tasks
    if self.async_startup_tasks:
        try:
            await asyncio.gather(
                *[
                    task[0](*task[1] if len(task) > 1 else [], **task[2] if len(task) == 3 else {})
                    for task in self.async_startup_tasks
                ]
            )
        except Exception as e:
            self.dispatch(events.Error(source="async-extension-loader", error=e))
    try:
        await self._connection_state.start()
    finally:
        await self.stop()

change_presence(status=Status.ONLINE, activity=None) async

Change the bots presence.

Parameters:

Name Type Description Default
status Optional[Union[str, Status]]

The status for the bot to be. i.e. online, afk, etc.

Status.ONLINE
activity Optional[Union[Activity, str]]

The activity for the bot to be displayed as doing.

None

Note

Bots may only be playing streaming listening watching competing or custom

Source code in interactions/client/client.py
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
async def change_presence(
    self,
    status: Optional[Union[str, Status]] = Status.ONLINE,
    activity: Optional[Union[Activity, str]] = None,
) -> None:
    """
    Change the bots presence.

    Args:
        status: The status for the bot to be. i.e. online, afk, etc.
        activity: The activity for the bot to be displayed as doing.

    !!! note
        Bots may only be `playing` `streaming` `listening` `watching`  `competing` or `custom`

    """
    await self._connection_state.change_presence(status, activity)

command(*args, **kwargs)

A decorator that registers a command. Aliases interactions.slash_command

Source code in interactions/client/client.py
1261
1262
1263
1264
1265
def command(self, *args, **kwargs) -> Callable:
    """A decorator that registers a command. Aliases `interactions.slash_command`"""
    raise NotImplementedError(
        "Use interactions.slash_command instead. Please consult the v4 -> v5 migration guide https://interactions-py.github.io/interactions.py/Guides/98%20Migration%20from%204.X/"
    )

connect_to_vc(guild_id, channel_id, muted=False, deafened=False) async

Connect the bot to a voice channel.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

id of the guild the voice channel is in.

required
channel_id Snowflake_Type

id of the voice channel client wants to join.

required
muted bool

Whether the bot should be muted when connected.

False
deafened bool

Whether the bot should be deafened when connected.

False

Returns:

Type Description
ActiveVoiceState

The new active voice state on successfully connection.

Source code in interactions/client/client.py
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
async def connect_to_vc(
    self,
    guild_id: "Snowflake_Type",
    channel_id: "Snowflake_Type",
    muted: bool = False,
    deafened: bool = False,
) -> ActiveVoiceState:
    """
    Connect the bot to a voice channel.

    Args:
        guild_id: id of the guild the voice channel is in.
        channel_id: id of the voice channel client wants to join.
        muted: Whether the bot should be muted when connected.
        deafened: Whether the bot should be deafened when connected.

    Returns:
        The new active voice state on successfully connection.

    """
    return await self._connection_state.voice_connect(guild_id, channel_id, muted, deafened)

consume_entitlement(entitlement_id) async

For One-Time Purchase consumable SKUs, marks a given entitlement for the user as consumed.

Parameters:

Name Type Description Default
entitlement_id Snowflake_Type

The ID of the entitlement to consume.

required
Source code in interactions/client/client.py
2656
2657
2658
2659
2660
2661
2662
2663
2664
async def consume_entitlement(self, entitlement_id: "Snowflake_Type") -> None:
    """
    For One-Time Purchase consumable SKUs, marks a given entitlement for the user as consumed.

    Args:
        entitlement_id: The ID of the entitlement to consume.

    """
    await self.http.consume_entitlement(self.app.id, entitlement_id)

create_guild_from_template(template_code, name, icon=MISSING) async

Creates a new guild based on a template.

Note

This endpoint can only be used by bots in less than 10 guilds.

Parameters:

Name Type Description Default
template_code Union[GuildTemplate, str]

The code of the template to use.

required
name str

The name of the guild (2-100 characters)

required
icon Absent[UPLOADABLE_TYPE]

Location or File of icon to set

MISSING

Returns:

Type Description
Optional[Guild]

The newly created guild object

Source code in interactions/client/client.py
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
async def create_guild_from_template(
    self,
    template_code: Union["GuildTemplate", str],
    name: str,
    icon: Absent[UPLOADABLE_TYPE] = MISSING,
) -> Optional[Guild]:
    """
    Creates a new guild based on a template.

    !!! note
        This endpoint can only be used by bots in less than 10 guilds.

    Args:
        template_code: The code of the template to use.
        name: The name of the guild (2-100 characters)
        icon: Location or File of icon to set

    Returns:
        The newly created guild object

    """
    if isinstance(template_code, GuildTemplate):
        template_code = template_code.code

    if icon:
        icon = to_image_data(icon)
    guild_data = await self.http.create_guild_from_guild_template(template_code, name, icon)
    return Guild.from_dict(guild_data, self)

create_test_entitlement(sku_id, owner_id, owner_type) async

Create a test entitlement for the bot's application.

Parameters:

Name Type Description Default
sku_id Snowflake_Type

The ID of the SKU to create the entitlement for.

required
owner_id Snowflake_Type

The ID of the owner of the entitlement.

required
owner_type int

The type of the owner of the entitlement. 1 for a guild subscription, 2 for a user subscription

required

Returns:

Type Description
Entitlement

The created entitlement.

Source code in interactions/client/client.py
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
async def create_test_entitlement(
    self, sku_id: "Snowflake_Type", owner_id: "Snowflake_Type", owner_type: int
) -> Entitlement:
    """
    Create a test entitlement for the bot's application.

    Args:
        sku_id: The ID of the SKU to create the entitlement for.
        owner_id: The ID of the owner of the entitlement.
        owner_type: The type of the owner of the entitlement. 1 for a guild subscription, 2 for a user subscription

    Returns:
        The created entitlement.

    """
    payload = {"sku_id": to_snowflake(sku_id), "owner_id": to_snowflake(owner_id), "owner_type": owner_type}

    entitlement_data = await self.http.create_test_entitlement(payload, self.app.id)
    return Entitlement.from_dict(entitlement_data, self)

default_error_handler(source, error) staticmethod

The default error logging behaviour.

Parameters:

Name Type Description Default
source str

The source of this error

required
error BaseException

The exception itself

required
Source code in interactions/client/client.py
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
@staticmethod
def default_error_handler(source: str, error: BaseException) -> None:
    """
    The default error logging behaviour.

    Args:
        source: The source of this error
        error: The exception itself

    """
    out = traceback.format_exception(error)

    if isinstance(error, HTTPException):
        # HTTPException's are of 3 known formats, we can parse them for human readable errors
        with contextlib.suppress(Exception):
            out = [str(error)]
    get_logger().error(
        "Ignoring exception in {}:{}{}".format(source, "\n" if len(out) > 1 else " ", "".join(out)),
    )

delete_test_entitlement(entitlement_id) async

Delete a test entitlement for the bot's application.

Parameters:

Name Type Description Default
entitlement_id Snowflake_Type

The ID of the entitlement to delete.

required
Source code in interactions/client/client.py
2646
2647
2648
2649
2650
2651
2652
2653
2654
async def delete_test_entitlement(self, entitlement_id: "Snowflake_Type") -> None:
    """
    Delete a test entitlement for the bot's application.

    Args:
        entitlement_id: The ID of the entitlement to delete.

    """
    await self.http.delete_test_entitlement(self.app.id, to_snowflake(entitlement_id))

dispatch(event, *args, **kwargs)

Dispatch an event.

Parameters:

Name Type Description Default
event events.BaseEvent

The event to be dispatched.

required
Source code in interactions/client/client.py
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
def dispatch(self, event: events.BaseEvent, *args, **kwargs) -> None:
    """
    Dispatch an event.

    Args:
        event: The event to be dispatched.

    """
    if listeners := self.listeners.get(event.resolved_name, []):
        self.logger.debug(f"Dispatching Event: {event.resolved_name}")
        event.bot = self
        for _listen in listeners:
            try:
                self._queue_task(_listen, event, *args, **kwargs)
            except Exception as e:
                raise BotException(
                    f"An error occurred attempting during {event.resolved_name} event processing"
                ) from e

    try:
        asyncio.get_running_loop()
        _ = asyncio.create_task(self._process_waits(event))  # noqa: RUF006
    except RuntimeError:
        # dispatch attempt before event loop is running
        self.async_startup_tasks.append((self._process_waits, (event,), {}))

    if "event" in self.listeners:
        # special meta event listener
        for _listen in self.listeners["event"]:
            self._queue_task(_listen, event, *args, **kwargs)

fetch_channel(channel_id, *, force=False) async

Fetch a channel.

Note

This method is an alias for the cache which will either return a cached object, or query discord for the object if its not already cached.

Parameters:

Name Type Description Default
channel_id Snowflake_Type

The ID of the channel to get

required
force bool

Whether to poll the API regardless of cache

False

Returns:

Type Description
Optional[TYPE_ALL_CHANNEL]

Channel Object if found, otherwise None

Source code in interactions/client/client.py
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
async def fetch_channel(self, channel_id: "Snowflake_Type", *, force: bool = False) -> Optional["TYPE_ALL_CHANNEL"]:
    """
    Fetch a channel.

    !!! note
        This method is an alias for the cache which will either return a cached object, or query discord for the object
        if its not already cached.

    Args:
        channel_id: The ID of the channel to get
        force: Whether to poll the API regardless of cache

    Returns:
        Channel Object if found, otherwise None

    """
    try:
        return await self.cache.fetch_channel(channel_id, force=force)
    except NotFound:
        return None

fetch_custom_emoji(emoji_id, guild_id, *, force=False) async

Fetch a custom emoji by id.

Parameters:

Name Type Description Default
emoji_id Snowflake_Type

The id of the custom emoji.

required
guild_id Snowflake_Type

The id of the guild the emoji belongs to.

required
force bool

Whether to poll the API regardless of cache.

False

Returns:

Type Description
Optional[CustomEmoji]

The custom emoji if found, otherwise None.

Source code in interactions/client/client.py
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
async def fetch_custom_emoji(
    self, emoji_id: "Snowflake_Type", guild_id: "Snowflake_Type", *, force: bool = False
) -> Optional[CustomEmoji]:
    """
    Fetch a custom emoji by id.

    Args:
        emoji_id: The id of the custom emoji.
        guild_id: The id of the guild the emoji belongs to.
        force: Whether to poll the API regardless of cache.

    Returns:
        The custom emoji if found, otherwise None.

    """
    try:
        return await self.cache.fetch_emoji(guild_id, emoji_id, force=force)
    except NotFound:
        return None

fetch_entitlements(*, user_id=None, sku_ids=None, before=None, after=None, limit=100, guild_id=None, exclude_ended=None) async

Fetch the entitlements for the bot's application.

Parameters:

Name Type Description Default
user_id Optional[Snowflake_Type]

The ID of the user to filter entitlements by.

None
sku_ids Optional[list[Snowflake_Type]]

The IDs of the SKUs to filter entitlements by.

None
before Optional[Snowflake_Type]

Get entitlements before this ID.

None
after Optional[Snowflake_Type]

Get entitlements after this ID.

None
limit Optional[int]

The maximum number of entitlements to return. Maximum is 100.

100
guild_id Optional[Snowflake_Type]

The ID of the guild to filter entitlements by.

None
exclude_ended Optional[bool]

Whether to exclude ended entitlements.

None

Returns:

Type Description
List[Entitlement]

A list of entitlements.

Source code in interactions/client/client.py
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
async def fetch_entitlements(
    self,
    *,
    user_id: "Optional[Snowflake_Type]" = None,
    sku_ids: "Optional[list[Snowflake_Type]]" = None,
    before: "Optional[Snowflake_Type]" = None,
    after: "Optional[Snowflake_Type]" = None,
    limit: Optional[int] = 100,
    guild_id: "Optional[Snowflake_Type]" = None,
    exclude_ended: Optional[bool] = None,
) -> List[Entitlement]:
    """
    Fetch the entitlements for the bot's application.

    Args:
        user_id: The ID of the user to filter entitlements by.
        sku_ids: The IDs of the SKUs to filter entitlements by.
        before: Get entitlements before this ID.
        after: Get entitlements after this ID.
        limit: The maximum number of entitlements to return. Maximum is 100.
        guild_id: The ID of the guild to filter entitlements by.
        exclude_ended: Whether to exclude ended entitlements.

    Returns:
        A list of entitlements.

    """
    entitlements_data = await self.http.get_entitlements(
        self.app.id,
        user_id=user_id,
        sku_ids=sku_ids,
        before=before,
        after=after,
        limit=limit,
        guild_id=guild_id,
        exclude_ended=exclude_ended,
    )
    return Entitlement.from_list(entitlements_data, self)

fetch_guild(guild_id, *, force=False) async

Fetch a guild.

Note

This method is an alias for the cache which will either return a cached object, or query discord for the object if its not already cached.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The ID of the guild to get

required
force bool

Whether to poll the API regardless of cache

False

Returns:

Type Description
Optional[Guild]

Guild Object if found, otherwise None

Source code in interactions/client/client.py
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
async def fetch_guild(self, guild_id: "Snowflake_Type", *, force: bool = False) -> Optional[Guild]:
    """
    Fetch a guild.

    !!! note
        This method is an alias for the cache which will either return a cached object, or query discord for the object
        if its not already cached.

    Args:
        guild_id: The ID of the guild to get
        force: Whether to poll the API regardless of cache

    Returns:
        Guild Object if found, otherwise None

    """
    try:
        return await self.cache.fetch_guild(guild_id, force=force)
    except NotFound:
        return None

fetch_member(user_id, guild_id, *, force=False) async

Fetch a member from a guild.

Note

This method is an alias for the cache which will either return a cached object, or query discord for the object if its not already cached.

Parameters:

Name Type Description Default
user_id Snowflake_Type

The ID of the member

required
guild_id Snowflake_Type

The ID of the guild to get the member from

required
force bool

Whether to poll the API regardless of cache

False

Returns:

Type Description
Optional[Member]

Member object if found, otherwise None

Source code in interactions/client/client.py
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
async def fetch_member(
    self, user_id: "Snowflake_Type", guild_id: "Snowflake_Type", *, force: bool = False
) -> Optional[Member]:
    """
    Fetch a member from a guild.

    !!! note
        This method is an alias for the cache which will either return a cached object, or query discord for the object
        if its not already cached.

    Args:
        user_id: The ID of the member
        guild_id: The ID of the guild to get the member from
        force: Whether to poll the API regardless of cache

    Returns:
        Member object if found, otherwise None

    """
    try:
        return await self.cache.fetch_member(guild_id, user_id, force=force)
    except NotFound:
        return None

fetch_nitro_packs() async

List the sticker packs available to Nitro subscribers.

Returns:

Type Description
Optional[List[StickerPack]]

A list of StickerPack objects if found, otherwise returns None

Source code in interactions/client/client.py
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
async def fetch_nitro_packs(self) -> Optional[List["StickerPack"]]:
    """
    List the sticker packs available to Nitro subscribers.

    Returns:
        A list of StickerPack objects if found, otherwise returns None

    """
    try:
        packs_data = await self.http.list_nitro_sticker_packs()
        return [StickerPack.from_dict(data, self) for data in packs_data]

    except NotFound:
        return None

fetch_scheduled_event(guild_id, scheduled_event_id, with_user_count=False) async

Fetch a scheduled event by id.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The ID of the guild to get the scheduled event from

required
scheduled_event_id Snowflake_Type

The ID of the scheduled event to get

required
with_user_count bool

Whether to include the user count in the response

False

Returns:

Type Description
Optional[ScheduledEvent]

The scheduled event if found, otherwise None

Source code in interactions/client/client.py
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
async def fetch_scheduled_event(
    self,
    guild_id: "Snowflake_Type",
    scheduled_event_id: "Snowflake_Type",
    with_user_count: bool = False,
) -> Optional["ScheduledEvent"]:
    """
    Fetch a scheduled event by id.

    Args:
        guild_id: The ID of the guild to get the scheduled event from
        scheduled_event_id: The ID of the scheduled event to get
        with_user_count: Whether to include the user count in the response

    Returns:
        The scheduled event if found, otherwise None

    """
    try:
        scheduled_event_data = await self.http.get_scheduled_event(guild_id, scheduled_event_id, with_user_count)
        return self.cache.place_scheduled_event_data(scheduled_event_data)
    except NotFound:
        return None

fetch_sticker(sticker_id) async

Fetch a sticker by ID.

Parameters:

Name Type Description Default
sticker_id Snowflake_Type

The ID of the sticker.

required

Returns:

Type Description
Optional[Sticker]

A sticker object if found, otherwise None

Source code in interactions/client/client.py
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
async def fetch_sticker(self, sticker_id: "Snowflake_Type") -> Optional[Sticker]:
    """
    Fetch a sticker by ID.

    Args:
        sticker_id: The ID of the sticker.

    Returns:
        A sticker object if found, otherwise None

    """
    try:
        sticker_data = await self.http.get_sticker(sticker_id)
        return Sticker.from_dict(sticker_data, self)
    except NotFound:
        return None

fetch_user(user_id, *, force=False) async

Fetch a user.

Note

This method is an alias for the cache which will either return a cached object, or query discord for the object if its not already cached.

Parameters:

Name Type Description Default
user_id Snowflake_Type

The ID of the user to get

required
force bool

Whether to poll the API regardless of cache

False

Returns:

Type Description
Optional[User]

User Object if found, otherwise None

Source code in interactions/client/client.py
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
async def fetch_user(self, user_id: "Snowflake_Type", *, force: bool = False) -> Optional[User]:
    """
    Fetch a user.

    !!! note
        This method is an alias for the cache which will either return a cached object, or query discord for the object
        if its not already cached.

    Args:
        user_id: The ID of the user to get
        force: Whether to poll the API regardless of cache

    Returns:
        User Object if found, otherwise None

    """
    try:
        return await self.cache.fetch_user(user_id, force=force)
    except NotFound:
        return None

fetch_voice_regions() async

List the voice regions available on Discord.

Returns:

Type Description
List[VoiceRegion]

A list of voice regions.

Source code in interactions/client/client.py
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
async def fetch_voice_regions(self) -> List["VoiceRegion"]:
    """
    List the voice regions available on Discord.

    Returns:
        A list of voice regions.

    """
    regions_data = await self.http.list_voice_regions()
    return VoiceRegion.from_list(regions_data)

get_application_cmd_by_id(cmd_id, *, scope=None)

Get a application command from the internal cache by its ID.

Parameters:

Name Type Description Default
cmd_id Snowflake_Type

The ID of the command

required
scope Snowflake_Type

Optionally specify a scope to search in

None

Returns:

Type Description
Optional[InteractionCommand]

The command, if one with the given ID exists internally, otherwise None

Source code in interactions/client/client.py
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
def get_application_cmd_by_id(
    self, cmd_id: "Snowflake_Type", *, scope: "Snowflake_Type" = None
) -> Optional[InteractionCommand]:
    """
    Get a application command from the internal cache by its ID.

    Args:
        cmd_id: The ID of the command
        scope: Optionally specify a scope to search in

    Returns:
        The command, if one with the given ID exists internally, otherwise None

    """
    cmd_id = to_snowflake(cmd_id)
    scope = to_snowflake(scope) if scope is not None else None

    if scope is not None:
        return next(
            (cmd for cmd in self.interactions_by_scope[scope].values() if cmd.get_cmd_id(scope) == cmd_id), None
        )
    return next(cmd for cmd in self._interaction_lookup.values() if cmd_id in cmd.cmd_id.values())

get_bot_voice_state(guild_id)

Get the bot's voice state for a guild.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The target guild's id.

required

Returns:

Type Description
Optional[ActiveVoiceState]

The bot's voice state for the guild if connected, otherwise None.

Source code in interactions/client/client.py
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
def get_bot_voice_state(self, guild_id: "Snowflake_Type") -> Optional[ActiveVoiceState]:
    """
    Get the bot's voice state for a guild.

    Args:
        guild_id: The target guild's id.

    Returns:
        The bot's voice state for the guild if connected, otherwise None.

    """
    return self._connection_state.get_voice_state(guild_id)

get_channel(channel_id)

Get a channel.

Note

This method is an alias for the cache which will return a cached object.

Parameters:

Name Type Description Default
channel_id Snowflake_Type

The ID of the channel to get

required

Returns:

Type Description
Optional[TYPE_ALL_CHANNEL]

Channel Object if found, otherwise None

Source code in interactions/client/client.py
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
def get_channel(self, channel_id: "Snowflake_Type") -> Optional["TYPE_ALL_CHANNEL"]:
    """
    Get a channel.

    !!! note
        This method is an alias for the cache which will return a cached object.

    Args:
        channel_id: The ID of the channel to get

    Returns:
        Channel Object if found, otherwise None

    """
    return self.cache.get_channel(channel_id)

get_custom_emoji(emoji_id, guild_id=None)

Get a custom emoji by id.

Parameters:

Name Type Description Default
emoji_id Snowflake_Type

The id of the custom emoji.

required
guild_id Optional[Snowflake_Type]

The id of the guild the emoji belongs to.

None

Returns:

Type Description
Optional[CustomEmoji]

The custom emoji if found, otherwise None.

Source code in interactions/client/client.py
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
def get_custom_emoji(
    self, emoji_id: "Snowflake_Type", guild_id: Optional["Snowflake_Type"] = None
) -> Optional[CustomEmoji]:
    """
    Get a custom emoji by id.

    Args:
        emoji_id: The id of the custom emoji.
        guild_id: The id of the guild the emoji belongs to.

    Returns:
        The custom emoji if found, otherwise None.

    """
    emoji = self.cache.get_emoji(emoji_id)
    if emoji and (not guild_id or emoji._guild_id == to_snowflake(guild_id)):
        return emoji
    return None

get_ext(name)

Get a extension with a name or extension name.

Parameters:

Name Type Description Default
name str

The name of the extension, or the name of it's extension

required

Returns:

Type Description
Extension | None

A extension, if found

Source code in interactions/client/client.py
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
def get_ext(self, name: str) -> Extension | None:
    """
    Get a extension with a name or extension name.

    Args:
        name: The name of the extension, or the name of it's extension

    Returns:
        A extension, if found

    """
    return ext[0] if (ext := self.get_extensions(name)) else None

get_extensions(name)

Get all ext with a name or extension name.

Parameters:

Name Type Description Default
name str

The name of the extension, or the name of it's extension

required

Returns:

Type Description
list[Extension]

List of Extensions

Source code in interactions/client/client.py
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
def get_extensions(self, name: str) -> list[Extension]:
    """
    Get all ext with a name or extension name.

    Args:
        name: The name of the extension, or the name of it's extension

    Returns:
        List of Extensions

    """
    if name not in self.ext.keys():
        return [ext for ext in self.ext.values() if ext.extension_name == name]

    return [self.ext.get(name, None)]

get_guild(guild_id)

Get a guild.

Note

This method is an alias for the cache which will return a cached object.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The ID of the guild to get

required

Returns:

Type Description
Optional[Guild]

Guild Object if found, otherwise None

Source code in interactions/client/client.py
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
def get_guild(self, guild_id: "Snowflake_Type") -> Optional[Guild]:
    """
    Get a guild.

    !!! note
        This method is an alias for the cache which will return a cached object.

    Args:
        guild_id: The ID of the guild to get

    Returns:
        Guild Object if found, otherwise None

    """
    return self.cache.get_guild(guild_id)

get_member(user_id, guild_id)

Get a member from a guild.

Note

This method is an alias for the cache which will return a cached object.

Parameters:

Name Type Description Default
user_id Snowflake_Type

The ID of the member

required
guild_id Snowflake_Type

The ID of the guild to get the member from

required

Returns:

Type Description
Optional[Member]

Member object if found, otherwise None

Source code in interactions/client/client.py
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
def get_member(self, user_id: "Snowflake_Type", guild_id: "Snowflake_Type") -> Optional[Member]:
    """
    Get a member from a guild.

    !!! note
        This method is an alias for the cache which will return a cached object.

    Args:
        user_id: The ID of the member
        guild_id: The ID of the guild to get the member from

    Returns:
        Member object if found, otherwise None

    """
    return self.cache.get_member(guild_id, user_id)

get_remote_commands(cmd_scope) async

Get the remote commands for a scope.

Parameters:

Name Type Description Default
cmd_scope Snowflake_Type

The scope to get the commands for.

required
Source code in interactions/client/client.py
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
async def get_remote_commands(self, cmd_scope: "Snowflake_Type") -> List[Dict[str, Any]]:
    """
    Get the remote commands for a scope.

    Args:
        cmd_scope: The scope to get the commands for.

    """
    try:
        return await self.http.get_application_commands(self.app.id, cmd_scope)
    except Forbidden:
        self.logger.warning(f"Bot is lacking `application.commands` scope in {cmd_scope}!")
        return []

get_scheduled_event(scheduled_event_id)

Get a scheduled event by id.

Note

This method is an alias for the cache which will return a cached object.

Parameters:

Name Type Description Default
scheduled_event_id Snowflake_Type

The ID of the scheduled event to get

required

Returns:

Type Description
Optional[ScheduledEvent]

The scheduled event if found, otherwise None

Source code in interactions/client/client.py
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
def get_scheduled_event(
    self,
    scheduled_event_id: "Snowflake_Type",
) -> Optional["ScheduledEvent"]:
    """
    Get a scheduled event by id.

    !!! note
        This method is an alias for the cache which will return a cached object.

    Args:
        scheduled_event_id: The ID of the scheduled event to get

    Returns:
        The scheduled event if found, otherwise None

    """
    return self.cache.get_scheduled_event(scheduled_event_id)

get_user(user_id)

Get a user.

Note

This method is an alias for the cache which will return a cached object.

Parameters:

Name Type Description Default
user_id Snowflake_Type

The ID of the user to get

required

Returns:

Type Description
Optional[User]

User Object if found, otherwise None

Source code in interactions/client/client.py
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
def get_user(self, user_id: "Snowflake_Type") -> Optional[User]:
    """
    Get a user.

    !!! note
        This method is an alias for the cache which will return a cached object.

    Args:
        user_id: The ID of the user to get

    Returns:
        User Object if found, otherwise None

    """
    return self.cache.get_user(user_id)

handle_pre_ready_response(data) async

Respond to an interaction that was received before the bot was ready.

Parameters:

Name Type Description Default
data dict

The interaction data

required
Source code in interactions/client/client.py
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
async def handle_pre_ready_response(self, data: dict) -> None:
    """
    Respond to an interaction that was received before the bot was ready.

    Args:
        data: The interaction data

    """
    if data["type"] == InteractionType.AUTOCOMPLETE:
        # we do not want to respond to autocompletes as discord will cache the response,
        # so we just ignore them
        return

    with contextlib.suppress(HTTPException):
        await self.http.post_initial_response(
            {
                "type": CallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
                "data": {
                    "content": f"{self.user.display_name} is starting up. Please try again in a few seconds",
                    "flags": MessageFlags.EPHEMERAL,
                },
            },
            token=data["token"],
            interaction_id=data["id"],
        )

listen(event_name=MISSING)

A decorator to be used in situations that the library can't automatically hook your listeners. Ideally, the standard listen decorator should be used, not this.

Parameters:

Name Type Description Default
event_name Absent[str]

The event name to use, if not the coroutine name

MISSING

Returns:

Type Description
Callable[[AsyncCallable], Listener]

A listener that can be used to hook into the event.

Source code in interactions/client/client.py
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
def listen(self, event_name: Absent[str] = MISSING) -> Callable[[AsyncCallable], Listener]:
    """
    A decorator to be used in situations that the library can't automatically hook your listeners. Ideally, the standard listen decorator should be used, not this.

    Args:
        event_name: The event name to use, if not the coroutine name

    Returns:
        A listener that can be used to hook into the event.

    """

    def wrapper(coro: AsyncCallable) -> Listener:
        listener = Listener.create(event_name)(coro)
        self.add_listener(listener)
        return listener

    return wrapper

load_extension(name, package=None, **load_kwargs)

Load an extension with given arguments.

Parameters:

Name Type Description Default
name str

The name of the extension.

required
package str | None

The package the extension is in

None
**load_kwargs Any

The auto-filled mapping of the load keyword arguments

{}
Source code in interactions/client/client.py
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
def load_extension(
    self,
    name: str,
    package: str | None = None,
    **load_kwargs: Any,
) -> None:
    """
    Load an extension with given arguments.

    Args:
        name: The name of the extension.
        package: The package the extension is in
        **load_kwargs: The auto-filled mapping of the load keyword arguments

    """
    module_name = importlib.util.resolve_name(name, package)
    if module_name in self.__modules:
        raise Exception(f"{module_name} already loaded")

    module = importlib.import_module(module_name, package)
    self.__load_module(module, module_name, **load_kwargs)

load_extensions(*packages, recursive=False, **load_kwargs)

Load multiple extensions at once.

Removes the need of manually looping through the package and loading the extensions.

Parameters:

Name Type Description Default
*packages str

The package(s) where the extensions are located.

()
recursive bool

Whether to load extensions from the subdirectories within the package.

False
Source code in interactions/client/client.py
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
def load_extensions(
    self,
    *packages: str,
    recursive: bool = False,
    **load_kwargs: Any,
) -> None:
    """
    Load multiple extensions at once.

    Removes the need of manually looping through the package
    and loading the extensions.

    Args:
        *packages: The package(s) where the extensions are located.
        recursive: Whether to load extensions from the subdirectories within the package.

    """
    if not packages:
        raise ValueError("You must specify at least one package.")

    for package in packages:
        # If recursive then include subdirectories ('**')
        # otherwise just the package specified by the user.
        pattern = os.path.join(package, "**" if recursive else "", "*.py")

        # Find all files matching the pattern, and convert slashes to dots.
        extensions = [f.replace(os.path.sep, ".").replace(".py", "") for f in glob.glob(pattern, recursive=True)]

        for ext in extensions:
            self.load_extension(ext, **load_kwargs)

login(token=None) async

Login to discord via http.

Note

You will need to run Client.start_gateway() before you start receiving gateway events.

Parameters:

Name Type Description Default
token str

Your bot's token

None
Source code in interactions/client/client.py
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
async def login(self, token: str | None = None) -> None:
    """
    Login to discord via http.

    !!! note
        You will need to run Client.start_gateway() before you start receiving gateway events.

    Args:
        token str: Your bot's token

    """
    if not self.token and not token:
        raise RuntimeError(
            "No token provided - please provide a token in the client constructor or via the login method."
        )
    self.token = (token or self.token).strip()

    # i needed somewhere to put this call,
    # login will always run after initialisation
    # so im gathering commands here
    self._gather_callbacks()

    if any(v for v in constants.CLIENT_FEATURE_FLAGS.values()):
        # list all enabled flags
        enabled_flags = [k for k, v in constants.CLIENT_FEATURE_FLAGS.items() if v]
        self.logger.info(f"Enabled feature flags: {', '.join(enabled_flags)}")

    self.logger.debug("Attempting to login")
    me = await self.http.login(self.token)
    self._user = ClientUser.from_dict(me, self)
    self.cache.place_user_data(me)
    self._app = Application.from_dict(await self.http.get_current_bot_information(), self)
    self._mention_reg = re.compile(rf"^(<@!?{self.user.id}*>\s)")

    if self.app.owner:
        self.owner_ids.add(self.app.owner.id)

    self.dispatch(events.Login())

mention_command(name, scope=0)

Returns a string that would mention the interaction specified.

Parameters:

Name Type Description Default
name str

The name of the interaction.

required
scope int

The scope of the interaction. Defaults to 0, the global scope.

0

Returns:

Name Type Description
str str

The interaction's mention in the specified scope.

Source code in interactions/client/client.py
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
def mention_command(self, name: str, scope: int = 0) -> str:
    """
    Returns a string that would mention the interaction specified.

    Args:
        name: The name of the interaction.
        scope: The scope of the interaction. Defaults to 0, the global scope.

    Returns:
        str: The interaction's mention in the specified scope.

    """
    return self.interactions_by_scope[scope][name].mention(scope)

on_autocomplete_completion(event) async

Called after any autocomplete callback is ran.

By default, it will simply log the autocomplete callback.

Listen to the AutocompleteCompletion event to overwrite this behaviour.

Source code in interactions/client/client.py
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
@Listener.create(is_default_listener=True)
async def on_autocomplete_completion(self, event: events.AutocompleteCompletion) -> None:
    """
    Called *after* any autocomplete callback is ran.

    By default, it will simply log the autocomplete callback.

    Listen to the `AutocompleteCompletion` event to overwrite this behaviour.

    """
    symbol = "$"
    self.logger.info(
        f"Autocomplete Called: {symbol}{event.ctx.invoke_target} with {event.ctx.focussed_option = } |"
        f" {event.ctx.kwargs = }"
    )

on_autocomplete_error(event) async

Catches all errors dispatched by autocompletion options.

By default it will dispatch the Error event.

Listen to the AutocompleteError event to overwrite this behaviour.

Source code in interactions/client/client.py
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
@Listener.create(is_default_listener=True)
async def on_autocomplete_error(self, event: events.AutocompleteError) -> None:
    """
    Catches all errors dispatched by autocompletion options.

    By default it will dispatch the `Error` event.

    Listen to the `AutocompleteError` event to overwrite this behaviour.

    """
    self.dispatch(
        events.Error(
            source=f"Autocomplete Callback for /{event.ctx.invoke_target} - Option: {event.ctx.focussed_option}",
            error=event.error,
            args=event.args,
            kwargs=event.kwargs,
            ctx=event.ctx,
        )
    )

on_command_completion(event) async

Called after any command is ran.

By default, it will simply log the command.

Listen to the CommandCompletion event to overwrite this behaviour.

Source code in interactions/client/client.py
737
738
739
740
741
742
743
744
745
746
747
@Listener.create(is_default_listener=True)
async def on_command_completion(self, event: events.CommandCompletion) -> None:
    """
    Called *after* any command is ran.

    By default, it will simply log the command.

    Listen to the `CommandCompletion` event to overwrite this behaviour.

    """
    self.logger.info(f"Command Called: {event.ctx.invoke_target} with {event.ctx.args = } | {event.ctx.kwargs = }")

on_command_error(event) async

Catches all errors dispatched by commands.

By default it will dispatch the Error event.

Listen to the CommandError event to overwrite this behaviour.

Source code in interactions/client/client.py
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
@Listener.create(is_default_listener=True)
async def on_command_error(self, event: events.CommandError) -> None:
    """
    Catches all errors dispatched by commands.

    By default it will dispatch the `Error` event.

    Listen to the `CommandError` event to overwrite this behaviour.

    """
    self.dispatch(
        events.Error(
            source=f"cmd `/{event.ctx.invoke_target}`",
            error=event.error,
            args=event.args,
            kwargs=event.kwargs,
            ctx=event.ctx,
        )
    )
    with contextlib.suppress(errors.LibraryException):
        if isinstance(event.error, errors.CommandOnCooldown):
            await event.ctx.send(
                embeds=Embed(
                    description=(
                        "This command is on cooldown!\n"
                        f"Please try again in {int(event.error.cooldown.get_cooldown_time())} seconds"
                    ),
                    color=BrandColors.FUCHSIA,
                )
            )
        elif isinstance(event.error, errors.MaxConcurrencyReached):
            await event.ctx.send(
                embeds=Embed(
                    description="This command has reached its maximum concurrent usage!\nPlease try again shortly.",
                    color=BrandColors.FUCHSIA,
                )
            )
        elif isinstance(event.error, errors.CommandCheckFailure):
            await event.ctx.send(
                embeds=Embed(
                    description="You do not have permission to run this command!",
                    color=BrandColors.YELLOW,
                )
            )
        elif self.send_command_tracebacks:
            out = "".join(traceback.format_exception(event.error))
            if self.http.token is not None:
                out = out.replace(self.http.token, "[REDACTED TOKEN]")
            await event.ctx.send(
                embeds=Embed(
                    title=f"Error: {type(event.error).__name__}",
                    color=BrandColors.RED,
                    description=f"```\n{out[:EMBED_MAX_DESC_LENGTH - 8]}```",
                )
            )

on_component_completion(event) async

Called after any component callback is ran.

By default, it will simply log the component use.

Listen to the ComponentCompletion event to overwrite this behaviour.

Source code in interactions/client/client.py
769
770
771
772
773
774
775
776
777
778
779
780
781
782
@Listener.create(is_default_listener=True)
async def on_component_completion(self, event: events.ComponentCompletion) -> None:
    """
    Called *after* any component callback is ran.

    By default, it will simply log the component use.

    Listen to the `ComponentCompletion` event to overwrite this behaviour.

    """
    symbol = "¢"
    self.logger.info(
        f"Component Called: {symbol}{event.ctx.invoke_target} with {event.ctx.args = } | {event.ctx.kwargs = }"
    )

on_component_error(event) async

Catches all errors dispatched by components.

By default it will dispatch the Error event.

Listen to the ComponentError event to overwrite this behaviour.

Source code in interactions/client/client.py
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
@Listener.create(is_default_listener=True)
async def on_component_error(self, event: events.ComponentError) -> None:
    """
    Catches all errors dispatched by components.

    By default it will dispatch the `Error` event.

    Listen to the `ComponentError` event to overwrite this behaviour.

    """
    self.dispatch(
        events.Error(
            source=f"Component Callback for {event.ctx.custom_id}",
            error=event.error,
            args=event.args,
            kwargs=event.kwargs,
            ctx=event.ctx,
        )
    )

on_error(event) async

Catches all errors dispatched by the library.

By default it will format and print them to console.

Listen to the Error event to overwrite this behaviour.

Source code in interactions/client/client.py
669
670
671
672
673
674
675
676
677
678
679
@Listener.create(is_default_listener=True)
async def on_error(self, event: events.Error) -> None:
    """
    Catches all errors dispatched by the library.

    By default it will format and print them to console.

    Listen to the `Error` event to overwrite this behaviour.

    """
    self.default_error_handler(event.source, event.error)

on_modal_completion(event) async

Called after any modal callback is ran.

By default, it will simply log the modal callback.

Listen to the ModalCompletion event to overwrite this behaviour.

Source code in interactions/client/client.py
840
841
842
843
844
845
846
847
848
849
850
@Listener.create(is_default_listener=True)
async def on_modal_completion(self, event: events.ModalCompletion) -> None:
    """
    Called *after* any modal callback is ran.

    By default, it will simply log the modal callback.

    Listen to the `ModalCompletion` event to overwrite this behaviour.

    """
    self.logger.info(f"Modal Called: {event.ctx.custom_id = } with {event.ctx.responses = }")

on_modal_error(event) async

Catches all errors dispatched by modals.

By default it will dispatch the Error event.

Listen to the ModalError event to overwrite this behaviour.

Source code in interactions/client/client.py
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
@Listener.create(is_default_listener=True)
async def on_modal_error(self, event: events.ModalError) -> None:
    """
    Catches all errors dispatched by modals.

    By default it will dispatch the `Error` event.

    Listen to the `ModalError` event to overwrite this behaviour.

    """
    self.dispatch(
        events.Error(
            source=f"Modal Callback for custom_id {event.ctx.custom_id}",
            error=event.error,
            args=event.args,
            kwargs=event.kwargs,
            ctx=event.ctx,
        )
    )

reload_extension(name, package=None, *, load_kwargs=None, unload_kwargs=None)

Helper method to reload an extension. Simply unloads, then loads the extension with given arguments.

Parameters:

Name Type Description Default
name str

The name of the extension.

required
package str | None

The package the extension is in

None
load_kwargs Any

The manually-filled mapping of the load keyword arguments

None
unload_kwargs Any

The manually-filled mapping of the unload keyword arguments

None
Source code in interactions/client/client.py
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
def reload_extension(
    self,
    name: str,
    package: str | None = None,
    *,
    load_kwargs: Any = None,
    unload_kwargs: Any = None,
) -> None:
    """
    Helper method to reload an extension. Simply unloads, then loads the extension with given arguments.

    Args:
        name: The name of the extension.
        package: The package the extension is in
        load_kwargs: The manually-filled mapping of the load keyword arguments
        unload_kwargs: The manually-filled mapping of the unload keyword arguments

    """
    name = importlib.util.resolve_name(name, package)
    module = self.__modules.get(name)

    if module is None:
        self.logger.warning("Attempted to reload extension thats not loaded. Loading extension instead")
        return self.load_extension(name, package)

    backup = module

    try:
        if not load_kwargs:
            load_kwargs = {}
        if not unload_kwargs:
            unload_kwargs = {}

        self.unload_extension(name, package, **unload_kwargs)
        self.load_extension(name, package, **load_kwargs)
    except Exception as e:
        try:
            self.logger.error(f"Error reloading extension {name}: {e} - attempting to revert to previous state")
            try:
                self.unload_extension(name, package, force=True, **unload_kwargs)  # make sure no remnants are left
            except Exception as t:
                self.logger.debug(f"Suppressing error unloading extension {name} during reload revert: {t}")

            sys.modules[name] = backup
            self.__load_module(backup, name, **load_kwargs)
            self.logger.info(f"Reverted extension {name} to previous state ", exc_info=e)
        except Exception as ex:
            sys.modules.pop(name, None)
            raise ex from e

start(token=None)

Start the bot.

If uvloop is installed, it will be used.

info

This is the recommended method to start the bot

Source code in interactions/client/client.py
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
def start(self, token: str | None = None) -> None:
    """
    Start the bot.

    If `uvloop` is installed, it will be used.

    info:
        This is the recommended method to start the bot
    """
    try:
        import uvloop

        has_uvloop = True
    except ImportError:
        has_uvloop = False

    with contextlib.suppress(KeyboardInterrupt):
        if has_uvloop:
            self.logger.info("uvloop is installed, using it")
            if sys.version_info >= (3, 11):
                with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner:
                    runner.run(self.astart(token))
            else:
                uvloop.install()
                asyncio.run(self.astart(token))
        else:
            asyncio.run(self.astart(token))

start_gateway() async

Starts the gateway connection.

Source code in interactions/client/client.py
1010
1011
1012
1013
1014
1015
async def start_gateway(self) -> None:
    """Starts the gateway connection."""
    try:
        await self._connection_state.start()
    finally:
        await self.stop()

stop() async

Shutdown the bot.

Source code in interactions/client/client.py
1017
1018
1019
1020
1021
1022
async def stop(self) -> None:
    """Shutdown the bot."""
    self.logger.debug("Stopping the bot.")
    self._ready.clear()
    await self.http.close()
    await self._connection_state.stop()

sync_scope(cmd_scope, delete_cmds, local_cmds_json) async

Sync a single scope.

Parameters:

Name Type Description Default
cmd_scope Snowflake_Type

The scope to sync.

required
delete_cmds bool

Whether to delete commands.

required
local_cmds_json Dict[Snowflake_Type, List[Dict[str, Any]]]

The local commands in json format.

required
Source code in interactions/client/client.py
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
async def sync_scope(
    self,
    cmd_scope: "Snowflake_Type",
    delete_cmds: bool,
    local_cmds_json: Dict["Snowflake_Type", List[Dict[str, Any]]],
) -> None:
    """
    Sync a single scope.

    Args:
        cmd_scope: The scope to sync.
        delete_cmds: Whether to delete commands.
        local_cmds_json: The local commands in json format.

    """
    sync_needed_flag = False
    sync_payload = []

    try:
        remote_commands = await self.get_remote_commands(cmd_scope)
        sync_payload, sync_needed_flag = self._build_sync_payload(
            remote_commands, cmd_scope, local_cmds_json, delete_cmds
        )

        if sync_needed_flag or (delete_cmds and len(sync_payload) < len(remote_commands)):
            await self._sync_commands_with_discord(sync_payload, cmd_scope)
        else:
            self.logger.debug(f"{cmd_scope} is already up-to-date with {len(remote_commands)} commands.")

    except Forbidden as e:
        raise InteractionMissingAccess(cmd_scope) from e
    except HTTPException as e:
        self._raise_sync_exception(e, local_cmds_json, cmd_scope)

synchronise_interactions(*, scopes=MISSING, delete_commands=MISSING) async

Synchronise registered interactions with discord.

Parameters:

Name Type Description Default
scopes Sequence[Snowflake_Type]

Optionally specify which scopes are to be synced.

MISSING
delete_commands Absent[bool]

Override the client setting and delete commands.

MISSING

Returns:

Type Description
None

None

Raises:

Type Description
InteractionMissingAccess

If bot is lacking the necessary access.

Exception

If there is an error during the synchronization process.

Source code in interactions/client/client.py
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
async def synchronise_interactions(
    self,
    *,
    scopes: Sequence["Snowflake_Type"] = MISSING,
    delete_commands: Absent[bool] = MISSING,
) -> None:
    """
    Synchronise registered interactions with discord.

    Args:
        scopes: Optionally specify which scopes are to be synced.
        delete_commands: Override the client setting and delete commands.

    Returns:
        None

    Raises:
        InteractionMissingAccess: If bot is lacking the necessary access.
        Exception: If there is an error during the synchronization process.

    """
    s = time.perf_counter()
    _delete_cmds = self.del_unused_app_cmd if delete_commands is MISSING else delete_commands
    await self._cache_interactions()

    cmd_scopes = self._get_sync_scopes(scopes)
    local_cmds_json = application_commands_to_dict(self.interactions_by_scope, self)

    await asyncio.gather(*[self.sync_scope(scope, _delete_cmds, local_cmds_json) for scope in cmd_scopes])

    t = time.perf_counter() - s
    self.logger.debug(f"Sync of {len(cmd_scopes)} scopes took {t} seconds")

unload_extension(name, package=None, force=False, **unload_kwargs)

Unload an extension with given arguments.

Parameters:

Name Type Description Default
name str

The name of the extension.

required
package str | None

The package the extension is in

None
force bool

Whether to force unload the extension - for use in reversions

False
**unload_kwargs Any

The auto-filled mapping of the unload keyword arguments

{}
Source code in interactions/client/client.py
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
def unload_extension(
    self, name: str, package: str | None = None, force: bool = False, **unload_kwargs: Any
) -> None:
    """
    Unload an extension with given arguments.

    Args:
        name: The name of the extension.
        package: The package the extension is in
        force: Whether to force unload the extension - for use in reversions
        **unload_kwargs: The auto-filled mapping of the unload keyword arguments

    """
    name = importlib.util.resolve_name(name, package)
    module = self.__modules.get(name)

    if module is None and not force:
        raise ExtensionNotFound(f"No extension called {name} is loaded")

    with contextlib.suppress(AttributeError):
        teardown = module.teardown
        teardown(**unload_kwargs)

    for ext in self.get_extensions(name):
        ext.drop(**unload_kwargs)

    sys.modules.pop(name, None)
    self.__modules.pop(name, None)

    if self.sync_ext and self._ready.is_set():
        try:
            asyncio.get_running_loop()
        except RuntimeError:
            return
        _ = asyncio.create_task(self.synchronise_interactions())  # noqa: RUF006

update_command_cache(scope, command_name, command_id)

Update the internal cache with a command ID.

Parameters:

Name Type Description Default
scope Snowflake_Type

The scope of the command to update

required
command_name str

The name of the command

required
command_id Snowflake

The ID of the command

required
Source code in interactions/client/client.py
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
def update_command_cache(self, scope: "Snowflake_Type", command_name: str, command_id: "Snowflake") -> None:
    """
    Update the internal cache with a command ID.

    Args:
        scope: The scope of the command to update
        command_name: The name of the command
        command_id: The ID of the command

    """
    if command := self.interactions_by_scope[scope].get(command_name):
        command.cmd_id[scope] = command_id
        self._interaction_lookup[command.resolved_name] = command

wait_for(event, checks=MISSING, timeout=None)

Waits for a WebSocket event to be dispatched.

Parameters:

Name Type Description Default
event Union[str, type[BaseEvent]]

The name of event to wait.

required
checks Absent[Callable[[BaseEvent], bool] | Callable[[BaseEvent], Awaitable[bool]]]

A predicate to check what to wait for.

MISSING
timeout Optional[float]

The number of seconds to wait before timing out.

None

Returns:

Type Description
Awaitable[Any]

The event object.

Source code in interactions/client/client.py
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
def wait_for(
    self,
    event: Union[str, "type[BaseEvent]"],
    checks: Absent[Callable[[BaseEvent], bool] | Callable[[BaseEvent], Awaitable[bool]]] = MISSING,
    timeout: Optional[float] = None,
) -> Awaitable[Any]:
    """
    Waits for a WebSocket event to be dispatched.

    Args:
        event: The name of event to wait.
        checks: A predicate to check what to wait for.
        timeout: The number of seconds to wait before timing out.

    Returns:
        The event object.

    """
    event = get_event_name(event)

    if event not in self.waits:
        self.waits[event] = []

    future = asyncio.Future()
    self.waits[event].append(Wait(event, checks, future))

    return asyncio.wait_for(future, timeout)

wait_for_component(messages=None, components=None, check=None, timeout=None) async

Waits for a component to be sent to the bot.

Parameters:

Name Type Description Default
messages Optional[Union[Message, int, list]]

The message object to check for.

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to wait for.

None
check Optional[Callable[[events.Component], bool] | Callable[[events.Component], Awaitable[bool]]]

A predicate to check what to wait for.

None
timeout Optional[float]

The number of seconds to wait before timing out.

None

Returns:

Type Description
Component

Component that was invoked. Use .ctx to get the ComponentContext.

Raises:

Type Description
asyncio.TimeoutError

if timed out

Source code in interactions/client/client.py
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
async def wait_for_component(
    self,
    messages: Optional[Union[Message, int, list]] = None,
    components: Optional[
        Union[
            List[List[Union["BaseComponent", dict]]],
            List[Union["BaseComponent", dict]],
            "BaseComponent",
            dict,
        ]
    ] = None,
    check: Optional[Callable[[events.Component], bool] | Callable[[events.Component], Awaitable[bool]]] = None,
    timeout: Optional[float] = None,
) -> "events.Component":
    """
    Waits for a component to be sent to the bot.

    Args:
        messages: The message object to check for.
        components: The components to wait for.
        check: A predicate to check what to wait for.
        timeout: The number of seconds to wait before timing out.

    Returns:
        `Component` that was invoked. Use `.ctx` to get the `ComponentContext`.

    Raises:
        asyncio.TimeoutError: if timed out

    """
    if not messages and not components:
        raise ValueError("You must specify messages or components (or both)")

    message_ids = (
        to_snowflake_list(messages) if isinstance(messages, list) else to_snowflake(messages) if messages else None
    )
    custom_ids = list(get_components_ids(components)) if components else None

    # automatically convert improper custom_ids
    if custom_ids and not all(isinstance(x, str) for x in custom_ids):
        custom_ids = [str(i) for i in custom_ids]

    async def _check(event: events.Component) -> bool:
        ctx: ComponentContext = event.ctx
        # if custom_ids is empty or there is a match
        wanted_message = not message_ids or ctx.message.id in (
            [message_ids] if isinstance(message_ids, int) else message_ids
        )
        wanted_component = not custom_ids or ctx.custom_id in custom_ids
        if wanted_message and wanted_component:
            if asyncio.iscoroutinefunction(check):
                return bool(check is None or await check(event))
            return bool(check is None or check(event))
        return False

    return await self.wait_for("component", checks=_check, timeout=timeout)

wait_for_modal(modal, author=None, timeout=None) async

Wait for a modal response.

Parameters:

Name Type Description Default
modal Modal

The modal we're waiting for.

required
author Optional[Snowflake_Type]

The user we're waiting for to reply

None
timeout Optional[float]

A timeout in seconds to stop waiting

None

Returns:

Type Description
ModalContext

The context of the modal response

Raises:

Type Description
asyncio.TimeoutError

if no response is received that satisfies the predicate before timeout seconds have passed

Source code in interactions/client/client.py
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
async def wait_for_modal(
    self,
    modal: "Modal",
    author: Optional["Snowflake_Type"] = None,
    timeout: Optional[float] = None,
) -> "ModalContext":
    """
    Wait for a modal response.

    Args:
        modal: The modal we're waiting for.
        author: The user we're waiting for to reply
        timeout: A timeout in seconds to stop waiting

    Returns:
        The context of the modal response

    Raises:
        asyncio.TimeoutError: if no response is received that satisfies the predicate before timeout seconds have passed

    """
    author = to_snowflake(author) if author else None

    def predicate(event: events.ModalCompletion) -> bool:
        if modal.custom_id != event.ctx.custom_id:
            return False
        return author == to_snowflake(event.ctx.author) if author else True

    resp = await self.wait_for("modal_completion", predicate, timeout)
    return resp.ctx

wait_until_ready() async

Waits for the client to become ready.

Source code in interactions/client/client.py
1066
1067
1068
async def wait_until_ready(self) -> None:
    """Waits for the client to become ready."""
    await self._ready.wait()

AutoShardedClient

Bases: Client

A client to automatically shard the bot.

You can optionally specify the total number of shards to start with, or it will be determined automatically.

Source code in interactions/client/auto_shard_client.py
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
class AutoShardedClient(Client):
    """
    A client to automatically shard the bot.

    You can optionally specify the total number of shards to start with, or it will be determined automatically.
    """

    def __init__(self, *args, **kwargs) -> None:
        self.auto_sharding = "total_shards" not in kwargs
        super().__init__(*args, **kwargs)

        self.shard_ids: Optional[List[int]] = kwargs.get("shard_ids", None)

        self._connection_state = None

        self._connection_states: list[ConnectionState] = []

        self.max_start_concurrency: int = 1

    @property
    def gateway_started(self) -> bool:
        """Returns if the gateway has been started in all shards."""
        return all(state.gateway_started.is_set() for state in self._connection_states)

    @property
    def shards(self) -> list[ConnectionState]:
        """Returns a list of all shards currently in use."""
        return self._connection_states

    @property
    def latency(self) -> float:
        """The average latency of all active gateways."""
        if len(self._connection_states):
            latencies = sum((g.latency for g in self._connection_states))
            return latencies / len(self._connection_states)
        return float("inf")

    @property
    def latencies(self) -> dict[int, float]:
        """
        Return a dictionary of latencies for all shards.

        Returns:
            {shard_id: latency}

        """
        return {state.shard_id: state.latency for state in self._connection_states}

    @property
    def start_time(self) -> datetime:
        """The start time of the first shard of the bot."""
        return next((state.start_time for state in self._connection_states), MISSING)  # type: ignore

    @property
    def start_times(self) -> dict[int, datetime]:
        """The start times of all shards of the bot, keyed by each shard ID."""
        return {state.shard_id: state.start_time for state in self._connection_states}  # type: ignore

    async def stop(self) -> None:
        """Shutdown the bot."""
        self.logger.debug("Stopping the bot.")
        self._ready.clear()
        await self.http.close()
        await asyncio.gather(*(state.stop() for state in self._connection_states))

    def get_guild_websocket(self, guild_id: "Snowflake_Type") -> GatewayClient:
        """
        Get the appropriate websocket for a given guild

        Args:
            guild_id: The ID of the guild

        Returns:
            A gateway client for the given ID

        """
        shard_id = (int(guild_id) >> 22) % self.total_shards
        return next((state for state in self._connection_states if state.shard_id == shard_id), MISSING).gateway

    def get_shards_guild(self, shard_id: int) -> list[Guild]:
        """
        Returns the guilds that the specified shard can see

        Args:
            shard_id: The ID of the shard

        Returns:
            A list of guilds

        """
        return [guild for key, guild in self.cache.guild_cache.items() if ((key >> 22) % self.total_shards) == shard_id]

    def get_shard_id(self, guild_id: "Snowflake_Type") -> int:
        """
        Get the shard ID for a given guild.

        Args:
            guild_id: The ID of the guild

        Returns:
            The shard ID for the guild

        """
        return (int(guild_id) >> 22) % self.total_shards

    @Listener.create()
    async def _on_websocket_ready(self, event: events.RawGatewayEvent) -> None:
        """
        Catches websocket ready and determines when to dispatch the client `READY` signal.

        Args:
            event: The websocket ready packet

        """
        connection_data = event.data
        expected_guilds = {to_snowflake(guild["id"]) for guild in connection_data["guilds"]}
        shard_id, total_shards = connection_data["shard"]
        connection_state = next((state for state in self._connection_states if state.shard_id == shard_id), None)

        if expected_guilds:
            while True:
                try:
                    await asyncio.wait_for(self._guild_event.wait(), self.guild_event_timeout)
                except asyncio.TimeoutError:
                    self.logger.warning("Timeout waiting for guilds cache: Not all guilds will be in cache")
                    break
                self._guild_event.clear()
                if all(self.cache.get_guild(g_id) is not None for g_id in expected_guilds):
                    # all guilds cached
                    break

            if self.fetch_members:
                self.logger.info(f"Shard {shard_id} is waiting for members to be chunked")
                await asyncio.gather(*(guild.chunked.wait() for guild in self.guilds if guild.id in expected_guilds))
        else:
            self.logger.warning(
                f"Shard {shard_id} reports it has 0 guilds, this is an indicator you may be using too many shards"
            )
        # noinspection PyProtectedMember
        connection_state._shard_ready.set()
        self.dispatch(ShardConnect(shard_id))
        self.logger.debug(f"Shard {shard_id} is now ready")

        # noinspection PyProtectedMember
        await asyncio.gather(*[shard._shard_ready.wait() for shard in self._connection_states])

        # run any pending startup tasks
        if self.async_startup_tasks:
            try:
                await asyncio.gather(
                    *[
                        task[0](*task[1] if len(task) > 1 else [], **task[2] if len(task) == 3 else {})
                        for task in self.async_startup_tasks
                    ]
                )
            except Exception as e:
                self.dispatch(events.Error(source="async-extension-loader", error=e))

        # cache slash commands
        if not self._startup:
            await self._init_interactions()

        if not self._ready.is_set():
            self._ready.set()
            if not self._startup:
                self._startup = True
                self.dispatch(events.Startup())
            self.dispatch(events.Ready())

    async def astart(self, token: str | None = None) -> None:
        """
        Asynchronous method to start the bot.

        Args:
            token: Your bot's token

        """
        self.logger.debug("Starting http client...")
        await self.login(token)

        tasks = []

        # Sort shards into their respective ratelimit buckets
        shard_buckets = defaultdict(list)
        for shard in self._connection_states:
            bucket = str(shard.shard_id % self.max_start_concurrency)
            shard_buckets[bucket].append(shard)

        for bucket in shard_buckets.values():
            for shard in bucket:
                self.logger.debug(f"Starting {shard.shard_id}")
                start = time.perf_counter()
                tasks.append(asyncio.create_task(shard.start()))

                if self.max_start_concurrency == 1:
                    # connection ratelimiting when discord has asked for one connection concurrently
                    # noinspection PyProtectedMember
                    await shard._shard_ready.wait()
                    await asyncio.sleep(5.1 - (time.perf_counter() - start))

            # wait for shards to finish starting
            # noinspection PyProtectedMember
            await asyncio.gather(*[shard._shard_ready.wait() for shard in self._connection_states])

        try:
            await asyncio.gather(*tasks)
        finally:
            await self.stop()

    async def login(self, token: str | None = None) -> None:
        """
        Login to discord via http.

        !!! note
            You will need to run Client.start_gateway() before you start receiving gateway events.

        Args:
            token str: Your bot's token

        """
        await super().login(token)
        data = await self.http.get_gateway_bot()

        self.max_start_concurrency = data["session_start_limit"]["max_concurrency"]
        if self.auto_sharding:
            self.total_shards = data["shards"]
        elif data["shards"] != self.total_shards:
            recommended_shards = data["shards"]
            self.logger.info(
                f"Discord recommends you start with {recommended_shards} shard{'s' if recommended_shards != 1 else ''} instead of {self.total_shards}"
            )

        self.logger.debug(f"Starting bot with {self.total_shards} shard{'s' if self.total_shards != 1 else ''}")

        if self.shard_ids:
            self._connection_states = [ConnectionState(self, self.intents, shard_id) for shard_id in self.shard_ids]
        else:
            self._connection_states = [
                ConnectionState(self, self.intents, shard_id) for shard_id in range(self.total_shards)
            ]

    async def change_presence(
        self,
        status: Optional[str | Status] = Status.ONLINE,
        activity: Optional[str | Activity] = None,
        *,
        shard_id: int | None = None,
    ) -> None:
        """
        Change the bot's presence.

        Args:
            status: The status for the bot to be. i.e. online, afk, etc.
            activity: The activity for the bot to be displayed as doing.
            shard_id: The shard to change the presence on. If not specified, the presence will be changed on all shards.

        !!! note
            Bots may only be `playing` `streaming` `listening` `watching` or `competing`, other activity types are likely to fail.

        """
        if shard_id is None:
            await asyncio.gather(*[shard.change_presence(status, activity) for shard in self._connection_states])
        else:
            await self._connection_states[shard_id].change_presence(status, activity)

gateway_started: bool property

Returns if the gateway has been started in all shards.

latencies: dict[int, float] property

Return a dictionary of latencies for all shards.

Returns:

Type Description
dict[int, float]

{shard_id: latency}

latency: float property

The average latency of all active gateways.

shards: list[ConnectionState] property

Returns a list of all shards currently in use.

start_time: datetime property

The start time of the first shard of the bot.

start_times: dict[int, datetime] property

The start times of all shards of the bot, keyed by each shard ID.

astart(token=None) async

Asynchronous method to start the bot.

Parameters:

Name Type Description Default
token str | None

Your bot's token

None
Source code in interactions/client/auto_shard_client.py
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
async def astart(self, token: str | None = None) -> None:
    """
    Asynchronous method to start the bot.

    Args:
        token: Your bot's token

    """
    self.logger.debug("Starting http client...")
    await self.login(token)

    tasks = []

    # Sort shards into their respective ratelimit buckets
    shard_buckets = defaultdict(list)
    for shard in self._connection_states:
        bucket = str(shard.shard_id % self.max_start_concurrency)
        shard_buckets[bucket].append(shard)

    for bucket in shard_buckets.values():
        for shard in bucket:
            self.logger.debug(f"Starting {shard.shard_id}")
            start = time.perf_counter()
            tasks.append(asyncio.create_task(shard.start()))

            if self.max_start_concurrency == 1:
                # connection ratelimiting when discord has asked for one connection concurrently
                # noinspection PyProtectedMember
                await shard._shard_ready.wait()
                await asyncio.sleep(5.1 - (time.perf_counter() - start))

        # wait for shards to finish starting
        # noinspection PyProtectedMember
        await asyncio.gather(*[shard._shard_ready.wait() for shard in self._connection_states])

    try:
        await asyncio.gather(*tasks)
    finally:
        await self.stop()

change_presence(status=Status.ONLINE, activity=None, *, shard_id=None) async

Change the bot's presence.

Parameters:

Name Type Description Default
status Optional[str | Status]

The status for the bot to be. i.e. online, afk, etc.

Status.ONLINE
activity Optional[str | Activity]

The activity for the bot to be displayed as doing.

None
shard_id int | None

The shard to change the presence on. If not specified, the presence will be changed on all shards.

None

Note

Bots may only be playing streaming listening watching or competing, other activity types are likely to fail.

Source code in interactions/client/auto_shard_client.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
async def change_presence(
    self,
    status: Optional[str | Status] = Status.ONLINE,
    activity: Optional[str | Activity] = None,
    *,
    shard_id: int | None = None,
) -> None:
    """
    Change the bot's presence.

    Args:
        status: The status for the bot to be. i.e. online, afk, etc.
        activity: The activity for the bot to be displayed as doing.
        shard_id: The shard to change the presence on. If not specified, the presence will be changed on all shards.

    !!! note
        Bots may only be `playing` `streaming` `listening` `watching` or `competing`, other activity types are likely to fail.

    """
    if shard_id is None:
        await asyncio.gather(*[shard.change_presence(status, activity) for shard in self._connection_states])
    else:
        await self._connection_states[shard_id].change_presence(status, activity)

get_guild_websocket(guild_id)

Get the appropriate websocket for a given guild

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The ID of the guild

required

Returns:

Type Description
GatewayClient

A gateway client for the given ID

Source code in interactions/client/auto_shard_client.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def get_guild_websocket(self, guild_id: "Snowflake_Type") -> GatewayClient:
    """
    Get the appropriate websocket for a given guild

    Args:
        guild_id: The ID of the guild

    Returns:
        A gateway client for the given ID

    """
    shard_id = (int(guild_id) >> 22) % self.total_shards
    return next((state for state in self._connection_states if state.shard_id == shard_id), MISSING).gateway

get_shard_id(guild_id)

Get the shard ID for a given guild.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The ID of the guild

required

Returns:

Type Description
int

The shard ID for the guild

Source code in interactions/client/auto_shard_client.py
119
120
121
122
123
124
125
126
127
128
129
130
def get_shard_id(self, guild_id: "Snowflake_Type") -> int:
    """
    Get the shard ID for a given guild.

    Args:
        guild_id: The ID of the guild

    Returns:
        The shard ID for the guild

    """
    return (int(guild_id) >> 22) % self.total_shards

get_shards_guild(shard_id)

Returns the guilds that the specified shard can see

Parameters:

Name Type Description Default
shard_id int

The ID of the shard

required

Returns:

Type Description
list[Guild]

A list of guilds

Source code in interactions/client/auto_shard_client.py
106
107
108
109
110
111
112
113
114
115
116
117
def get_shards_guild(self, shard_id: int) -> list[Guild]:
    """
    Returns the guilds that the specified shard can see

    Args:
        shard_id: The ID of the shard

    Returns:
        A list of guilds

    """
    return [guild for key, guild in self.cache.guild_cache.items() if ((key >> 22) % self.total_shards) == shard_id]

login(token=None) async

Login to discord via http.

Note

You will need to run Client.start_gateway() before you start receiving gateway events.

Parameters:

Name Type Description Default
token str

Your bot's token

None
Source code in interactions/client/auto_shard_client.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
async def login(self, token: str | None = None) -> None:
    """
    Login to discord via http.

    !!! note
        You will need to run Client.start_gateway() before you start receiving gateway events.

    Args:
        token str: Your bot's token

    """
    await super().login(token)
    data = await self.http.get_gateway_bot()

    self.max_start_concurrency = data["session_start_limit"]["max_concurrency"]
    if self.auto_sharding:
        self.total_shards = data["shards"]
    elif data["shards"] != self.total_shards:
        recommended_shards = data["shards"]
        self.logger.info(
            f"Discord recommends you start with {recommended_shards} shard{'s' if recommended_shards != 1 else ''} instead of {self.total_shards}"
        )

    self.logger.debug(f"Starting bot with {self.total_shards} shard{'s' if self.total_shards != 1 else ''}")

    if self.shard_ids:
        self._connection_states = [ConnectionState(self, self.intents, shard_id) for shard_id in self.shard_ids]
    else:
        self._connection_states = [
            ConnectionState(self, self.intents, shard_id) for shard_id in range(self.total_shards)
        ]

stop() async

Shutdown the bot.

Source code in interactions/client/auto_shard_client.py
85
86
87
88
89
90
async def stop(self) -> None:
    """Shutdown the bot."""
    self.logger.debug("Stopping the bot.")
    self._ready.clear()
    await self.http.close()
    await asyncio.gather(*(state.stop() for state in self._connection_states))

ActiveVoiceState

Bases: VoiceState

Source code in interactions/models/internal/active_voice_state.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ActiveVoiceState(VoiceState):
    ws: Optional[VoiceGateway] = attrs.field(repr=False, default=None)
    """The websocket for this voice state"""
    player: Optional[Player] = attrs.field(repr=False, default=None)
    """The playback task that broadcasts audio data to discord"""
    recorder: Optional[Recorder] = attrs.field(default=None)
    """A recorder task to capture audio from discord"""
    _volume: float = attrs.field(repr=False, default=0.5)

    # standard voice states expect this data, this voice state lacks it initially; so we make them optional
    user_id: "Snowflake_Type" = attrs.field(repr=False, default=MISSING, converter=optional(to_snowflake))
    _guild_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None, converter=optional(to_snowflake))
    _member_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None, converter=optional(to_snowflake))

    def __attrs_post_init__(self) -> None:
        # jank line to handle the two inherently incompatible data structures
        self._member_id = self.user_id = self._client.user.id

    def __del__(self) -> None:
        if self.connected:
            self.ws.close()
        if self.player:
            self.player.stop()

    def __repr__(self) -> str:
        return f"<ActiveVoiceState: channel={self.channel} guild={self.guild} volume={self.volume} playing={self.playing} audio={self.current_audio}>"

    @property
    def current_audio(self) -> Optional["BaseAudio"]:
        """The current audio being played"""
        if self.player:
            return self.player.current_audio

    @property
    def volume(self) -> float:
        """Get the volume of the player"""
        return self._volume

    @volume.setter
    def volume(self, value) -> None:
        """Set the volume of the player"""
        if value < 0.0:
            raise ValueError("Volume may not be negative.")
        self._volume = value
        if self.player and hasattr(self.player.current_audio, "volume"):
            self.player.current_audio.volume = value

    @property
    def paused(self) -> bool:
        """Is the player currently paused"""
        return self.player.paused if self.player else False

    @property
    def playing(self) -> bool:
        """Are we currently playing something?"""
        # noinspection PyProtectedMember
        return bool(self.player and self.current_audio and not self.player.stopped and self.player._resume.is_set())

    @property
    def stopped(self) -> bool:
        """Is the player stopped?"""
        return self.player.stopped if self.player else True

    @property
    def connected(self) -> bool:
        """Is this voice state currently connected?"""
        # noinspection PyProtectedMember
        return False if self.ws is None else self.ws._closed.is_set()

    @property
    def gateway(self) -> "GatewayClient":
        return self._client.get_guild_websocket(self._guild_id)

    async def wait_for_stopped(self) -> None:
        """Wait for the player to stop playing."""
        if self.player:
            # noinspection PyProtectedMember
            await self.player._stopped.wait()

    async def _ws_connect(self) -> None:
        """Runs the voice gateway connection"""
        async with self.ws:
            try:
                await self.ws.run()
            finally:
                if self.playing:
                    await self.stop()

    async def ws_connect(self) -> None:
        """Connect to the voice gateway for this voice state"""
        self.ws = VoiceGateway(self._client._connection_state, self._voice_state.data, self._voice_server.data)

        _ = asyncio.create_task(self._ws_connect())  # noqa: RUF006
        await self.ws.wait_until_ready()

    def _guild_predicate(self, event) -> bool:
        return int(event.data["guild_id"]) == self._guild_id

    async def connect(self, timeout: int = 5) -> None:
        """
        Establish the voice connection.

        Args:
            timeout: How long to wait for state and server information from discord

        Raises:
            VoiceAlreadyConnected: if the voice state is already connected to the voice channel
            VoiceConnectionTimeout: if the voice state fails to connect

        """
        if self.connected:
            raise VoiceAlreadyConnected

        if Intents.GUILD_VOICE_STATES not in self._client.intents:
            raise RuntimeError("Cannot connect to voice without the GUILD_VOICE_STATES intent.")

        tasks = [
            asyncio.create_task(
                self._client.wait_for("raw_voice_state_update", self._guild_predicate, timeout=timeout)
            ),
            asyncio.create_task(
                self._client.wait_for("raw_voice_server_update", self._guild_predicate, timeout=timeout)
            ),
        ]

        await self.gateway.voice_state_update(self._guild_id, self._channel_id, self.self_mute, self.self_deaf)

        self.logger.debug("Waiting for voice connection data...")

        try:
            self._voice_state, self._voice_server = await asyncio.gather(*tasks)
        except asyncio.TimeoutError:
            raise VoiceConnectionTimeout from None

        self.logger.debug("Attempting to initialise voice gateway...")
        await self.ws_connect()

    async def disconnect(self) -> None:
        """Disconnect from the voice channel."""
        await self.gateway.voice_state_update(self._guild_id, None)

    async def move(self, channel: "Snowflake_Type", timeout: int = 5) -> None:
        """
        Move to another voice channel.

        Args:
            channel: The channel to move to
            timeout: How long to wait for state and server information from discord

        """
        target_channel = to_snowflake(channel)
        if target_channel != self._channel_id:
            already_paused = self.paused
            if self.player:
                self.player.pause()

            self._channel_id = target_channel
            await self.gateway.voice_state_update(self._guild_id, self._channel_id, self.self_mute, self.self_deaf)

            self.logger.debug("Waiting for voice connection data...")
            try:
                await self._client.wait_for("raw_voice_state_update", self._guild_predicate, timeout=timeout)
            except asyncio.TimeoutError:
                await self._close_connection()
                raise VoiceConnectionTimeout from None

            if self.player and not already_paused:
                self.player.resume()

    async def stop(self) -> None:
        """Stop playback."""
        self.player.stop()
        await self.player._stopped.wait()

    def pause(self) -> None:
        """Pause playback"""
        self.player.pause()

    def resume(self) -> None:
        """Resume playback."""
        self.player.resume()

    async def play(self, audio: "BaseAudio") -> None:
        """
        Start playing an audio object.

        Waits for the player to stop before returning.

        Args:
            audio: The audio object to play

        """
        if self.player:
            await self.stop()

        with Player(audio, self, asyncio.get_running_loop()) as self.player:
            self.player.play()
            await self.wait_for_stopped()

    def play_no_wait(self, audio: "BaseAudio") -> asyncio.Task:
        """
        Start playing an audio object, but don't wait for playback to finish.

        Args:
            audio: The audio object to play

        """
        return asyncio.create_task(self.play(audio))

    def create_recorder(self) -> Recorder:
        """Create a recorder instance."""
        if not self.recorder:
            self.recorder = Recorder(self, asyncio.get_running_loop())
        return self.recorder

    async def start_recording(self, encoding: Optional[str] = None, *, output_dir: str | Missing = Missing) -> Recorder:
        """
        Start recording the voice channel.

        If no recorder exists, one will be created.

        Args:
            encoding: What format the audio should be encoded to.
            output_dir: The directory to save the audio to

        """
        if not self.recorder:
            self.recorder = Recorder(self, asyncio.get_running_loop())

        if self.recorder.used:
            if self.recorder.recording:
                raise RuntimeError("Another recording is still in progress, please stop it first.")
            self.recorder = Recorder(self, asyncio.get_running_loop())

        if encoding is not None:
            self.recorder.encoding = encoding

        await self.recorder.start_recording(output_dir=output_dir)
        return self.recorder

    async def stop_recording(self) -> dict[int, BytesIO]:
        """
        Stop the recording.

        Returns:
            dict[snowflake, BytesIO]: The recorded audio

        """
        if not self.recorder or not self.recorder.recording or not self.recorder.audio:
            raise RuntimeError("No recorder is running!")
        await self.recorder.stop_recording()

        self.recorder.audio.finished.wait()
        return self.recordings

    @property
    def recordings(self) -> dict[int, BytesIO]:
        return self.recorder.output if self.recorder else {}

    async def _voice_server_update(self, data) -> None:
        """
        An internal receiver for voice server events.

        Args:
            data: voice server data

        """
        self.ws.set_new_voice_server(data)

    async def _voice_state_update(
        self,
        before: Optional[VoiceState],
        after: Optional[VoiceState],
        data: Optional[VoiceStateData],
    ) -> None:
        """
        An internal receiver for voice server state events.

        Args:
            before: The previous voice state
            after: The current voice state
            data: Raw data from gateway

        """
        if after is None:
            # bot disconnected
            self.logger.info(f"Disconnecting from voice channel {self._channel_id}")
            await self._close_connection()
            self._client.cache.delete_bot_voice_state(self._guild_id)
            return

        self.update_from_dict(data)

    async def _close_connection(self) -> None:
        """Close the voice connection."""
        if self.playing:
            await self.stop()
        if self.connected:
            self.ws.close()

connected: bool property

Is this voice state currently connected?

current_audio: Optional[BaseAudio] property

The current audio being played

paused: bool property

Is the player currently paused

player: Optional[Player] = attrs.field(repr=False, default=None) class-attribute

The playback task that broadcasts audio data to discord

playing: bool property

Are we currently playing something?

recorder: Optional[Recorder] = attrs.field(default=None) class-attribute

A recorder task to capture audio from discord

stopped: bool property

Is the player stopped?

volume: float writable property

Get the volume of the player

ws: Optional[VoiceGateway] = attrs.field(repr=False, default=None) class-attribute

The websocket for this voice state

connect(timeout=5) async

Establish the voice connection.

Parameters:

Name Type Description Default
timeout int

How long to wait for state and server information from discord

5

Raises:

Type Description
VoiceAlreadyConnected

if the voice state is already connected to the voice channel

VoiceConnectionTimeout

if the voice state fails to connect

Source code in interactions/models/internal/active_voice_state.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
async def connect(self, timeout: int = 5) -> None:
    """
    Establish the voice connection.

    Args:
        timeout: How long to wait for state and server information from discord

    Raises:
        VoiceAlreadyConnected: if the voice state is already connected to the voice channel
        VoiceConnectionTimeout: if the voice state fails to connect

    """
    if self.connected:
        raise VoiceAlreadyConnected

    if Intents.GUILD_VOICE_STATES not in self._client.intents:
        raise RuntimeError("Cannot connect to voice without the GUILD_VOICE_STATES intent.")

    tasks = [
        asyncio.create_task(
            self._client.wait_for("raw_voice_state_update", self._guild_predicate, timeout=timeout)
        ),
        asyncio.create_task(
            self._client.wait_for("raw_voice_server_update", self._guild_predicate, timeout=timeout)
        ),
    ]

    await self.gateway.voice_state_update(self._guild_id, self._channel_id, self.self_mute, self.self_deaf)

    self.logger.debug("Waiting for voice connection data...")

    try:
        self._voice_state, self._voice_server = await asyncio.gather(*tasks)
    except asyncio.TimeoutError:
        raise VoiceConnectionTimeout from None

    self.logger.debug("Attempting to initialise voice gateway...")
    await self.ws_connect()

create_recorder()

Create a recorder instance.

Source code in interactions/models/internal/active_voice_state.py
235
236
237
238
239
def create_recorder(self) -> Recorder:
    """Create a recorder instance."""
    if not self.recorder:
        self.recorder = Recorder(self, asyncio.get_running_loop())
    return self.recorder

disconnect() async

Disconnect from the voice channel.

Source code in interactions/models/internal/active_voice_state.py
163
164
165
async def disconnect(self) -> None:
    """Disconnect from the voice channel."""
    await self.gateway.voice_state_update(self._guild_id, None)

move(channel, timeout=5) async

Move to another voice channel.

Parameters:

Name Type Description Default
channel Snowflake_Type

The channel to move to

required
timeout int

How long to wait for state and server information from discord

5
Source code in interactions/models/internal/active_voice_state.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
async def move(self, channel: "Snowflake_Type", timeout: int = 5) -> None:
    """
    Move to another voice channel.

    Args:
        channel: The channel to move to
        timeout: How long to wait for state and server information from discord

    """
    target_channel = to_snowflake(channel)
    if target_channel != self._channel_id:
        already_paused = self.paused
        if self.player:
            self.player.pause()

        self._channel_id = target_channel
        await self.gateway.voice_state_update(self._guild_id, self._channel_id, self.self_mute, self.self_deaf)

        self.logger.debug("Waiting for voice connection data...")
        try:
            await self._client.wait_for("raw_voice_state_update", self._guild_predicate, timeout=timeout)
        except asyncio.TimeoutError:
            await self._close_connection()
            raise VoiceConnectionTimeout from None

        if self.player and not already_paused:
            self.player.resume()

pause()

Pause playback

Source code in interactions/models/internal/active_voice_state.py
200
201
202
def pause(self) -> None:
    """Pause playback"""
    self.player.pause()

play(audio) async

Start playing an audio object.

Waits for the player to stop before returning.

Parameters:

Name Type Description Default
audio BaseAudio

The audio object to play

required
Source code in interactions/models/internal/active_voice_state.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
async def play(self, audio: "BaseAudio") -> None:
    """
    Start playing an audio object.

    Waits for the player to stop before returning.

    Args:
        audio: The audio object to play

    """
    if self.player:
        await self.stop()

    with Player(audio, self, asyncio.get_running_loop()) as self.player:
        self.player.play()
        await self.wait_for_stopped()

play_no_wait(audio)

Start playing an audio object, but don't wait for playback to finish.

Parameters:

Name Type Description Default
audio BaseAudio

The audio object to play

required
Source code in interactions/models/internal/active_voice_state.py
225
226
227
228
229
230
231
232
233
def play_no_wait(self, audio: "BaseAudio") -> asyncio.Task:
    """
    Start playing an audio object, but don't wait for playback to finish.

    Args:
        audio: The audio object to play

    """
    return asyncio.create_task(self.play(audio))

resume()

Resume playback.

Source code in interactions/models/internal/active_voice_state.py
204
205
206
def resume(self) -> None:
    """Resume playback."""
    self.player.resume()

start_recording(encoding=None, *, output_dir=Missing) async

Start recording the voice channel.

If no recorder exists, one will be created.

Parameters:

Name Type Description Default
encoding Optional[str]

What format the audio should be encoded to.

None
output_dir str | Missing

The directory to save the audio to

Missing
Source code in interactions/models/internal/active_voice_state.py
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
async def start_recording(self, encoding: Optional[str] = None, *, output_dir: str | Missing = Missing) -> Recorder:
    """
    Start recording the voice channel.

    If no recorder exists, one will be created.

    Args:
        encoding: What format the audio should be encoded to.
        output_dir: The directory to save the audio to

    """
    if not self.recorder:
        self.recorder = Recorder(self, asyncio.get_running_loop())

    if self.recorder.used:
        if self.recorder.recording:
            raise RuntimeError("Another recording is still in progress, please stop it first.")
        self.recorder = Recorder(self, asyncio.get_running_loop())

    if encoding is not None:
        self.recorder.encoding = encoding

    await self.recorder.start_recording(output_dir=output_dir)
    return self.recorder

stop() async

Stop playback.

Source code in interactions/models/internal/active_voice_state.py
195
196
197
198
async def stop(self) -> None:
    """Stop playback."""
    self.player.stop()
    await self.player._stopped.wait()

stop_recording() async

Stop the recording.

Returns:

Type Description
dict[int, BytesIO]

dict[snowflake, BytesIO]: The recorded audio

Source code in interactions/models/internal/active_voice_state.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
async def stop_recording(self) -> dict[int, BytesIO]:
    """
    Stop the recording.

    Returns:
        dict[snowflake, BytesIO]: The recorded audio

    """
    if not self.recorder or not self.recorder.recording or not self.recorder.audio:
        raise RuntimeError("No recorder is running!")
    await self.recorder.stop_recording()

    self.recorder.audio.finished.wait()
    return self.recordings

wait_for_stopped() async

Wait for the player to stop playing.

Source code in interactions/models/internal/active_voice_state.py
 99
100
101
102
103
async def wait_for_stopped(self) -> None:
    """Wait for the player to stop playing."""
    if self.player:
        # noinspection PyProtectedMember
        await self.player._stopped.wait()

ws_connect() async

Connect to the voice gateway for this voice state

Source code in interactions/models/internal/active_voice_state.py
114
115
116
117
118
119
async def ws_connect(self) -> None:
    """Connect to the voice gateway for this voice state"""
    self.ws = VoiceGateway(self._client._connection_state, self._voice_state.data, self._voice_server.data)

    _ = asyncio.create_task(self._ws_connect())  # noqa: RUF006
    await self.ws.wait_until_ready()

User

BaseUser

Bases: DiscordObject, _SendDMMixin

Base class for User, essentially partial user discord model.

Source code in interactions/models/discord/user.py
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class BaseUser(DiscordObject, _SendDMMixin):
    """Base class for User, essentially partial user discord model."""

    username: str = attrs.field(repr=True, metadata=docs("The user's username, not unique across the platform"))
    global_name: str | None = attrs.field(
        repr=True, metadata=docs("The user's chosen display name, platform-wide"), default=None
    )
    discriminator: str = attrs.field(
        repr=True, metadata=docs("The user's 4-digit discord-tag"), default="0"
    )  # will likely be removed in future api version
    avatar: "Asset" = attrs.field(repr=False, metadata=docs("The user's default avatar"))

    def __str__(self) -> str:
        return self.tag

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if not isinstance(data["avatar"], Asset):
            if data["avatar"]:
                data["avatar"] = Asset.from_path_hash(client, f"avatars/{data['id']}/{{}}", data["avatar"])
            elif data["discriminator"] == "0":
                data["avatar"] = Asset(client, f"{Asset.BASE}/embed/avatars/{(int(data['id']) >> 22) % 6}")
            else:
                data["avatar"] = Asset(client, f"{Asset.BASE}/embed/avatars/{int(data['discriminator']) % 5}")
        return data

    @property
    def tag(self) -> str:
        """Returns the user's Discord tag."""
        if self.discriminator == "0":
            return f"@{self.username}"
        return f"{self.username}#{self.discriminator}"

    @property
    def mention(self) -> str:
        """Returns a string that would mention the user."""
        return f"<@{self.id}>"

    @property
    def display_name(self) -> str:
        """The users display name, will return nickname if one is set, otherwise will return username."""
        return self.global_name or self.username

    @property
    def display_avatar(self) -> "Asset":
        """The users displayed avatar, will return `guild_avatar` if one is set, otherwise will return user avatar."""
        return self.avatar

    @property
    def avatar_url(self) -> str:
        """The users avatar url."""
        return self.display_avatar.url

    async def fetch_dm(self, *, force: bool = False) -> "DM":
        """
        Fetch the DM channel associated with this user.

        Args:
            force: Whether to force a fetch from the API

        """
        return await self._client.cache.fetch_dm_channel(self.id, force=force)

    def get_dm(self) -> Optional["DM"]:
        """Get the DM channel associated with this user."""
        return self._client.cache.get_dm_channel(self.id)

    @property
    def mutual_guilds(self) -> List["Guild"]:
        """
        Get a list of mutual guilds shared between this user and the client.

        !!! note
            This will only be accurate if the guild members are cached internally
        """
        return [
            guild for guild in self._client.guilds if self._client.cache.get_member(guild_id=guild.id, user_id=self.id)
        ]

avatar_url: str property

The users avatar url.

display_avatar: Asset property

The users displayed avatar, will return guild_avatar if one is set, otherwise will return user avatar.

display_name: str property

The users display name, will return nickname if one is set, otherwise will return username.

mention: str property

Returns a string that would mention the user.

mutual_guilds: List[Guild] property

Get a list of mutual guilds shared between this user and the client.

Note

This will only be accurate if the guild members are cached internally

tag: str property

Returns the user's Discord tag.

fetch_dm(*, force=False) async

Fetch the DM channel associated with this user.

Parameters:

Name Type Description Default
force bool

Whether to force a fetch from the API

False
Source code in interactions/models/discord/user.py
100
101
102
103
104
105
106
107
108
async def fetch_dm(self, *, force: bool = False) -> "DM":
    """
    Fetch the DM channel associated with this user.

    Args:
        force: Whether to force a fetch from the API

    """
    return await self._client.cache.fetch_dm_channel(self.id, force=force)

get_dm()

Get the DM channel associated with this user.

Source code in interactions/models/discord/user.py
110
111
112
def get_dm(self) -> Optional["DM"]:
    """Get the DM channel associated with this user."""
    return self._client.cache.get_dm_channel(self.id)

ClientUser

Bases: User

Source code in interactions/models/discord/user.py
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ClientUser(User):
    verified: bool = attrs.field(repr=True, metadata={"docs": "Whether the email on this account has been verified"})
    mfa_enabled: bool = attrs.field(
        repr=False,
        default=False,
        metadata={"docs": "Whether the user has two factor enabled on their account"},
    )
    email: Optional[str] = attrs.field(
        repr=False, default=None, metadata={"docs": "the user's email"}
    )  # needs special permissions?
    locale: Optional[str] = attrs.field(
        repr=False, default=None, metadata={"docs": "the user's chosen language option"}
    )
    bio: Optional[str] = attrs.field(repr=False, default=None, metadata={"docs": ""})
    flags: "UserFlags" = attrs.field(
        repr=False,
        default=0,
        converter=UserFlags,
        metadata={"docs": "the flags on a user's account"},
    )

    _guild_ids: Set["Snowflake_Type"] = attrs.field(
        repr=False, factory=set, metadata={"docs": "All the guilds the user is in"}
    )

    def _add_guilds(self, guild_ids: Set["Snowflake_Type"]) -> None:
        """
        Add the guilds that the user is in to the internal reference.

        Args:
            guild_ids: The guild ids to add

        """
        self._guild_ids |= guild_ids

    @property
    def guilds(self) -> List["Guild"]:
        """The guilds the user is in."""
        return list(filter(None, (self._client.cache.get_guild(guild_id) for guild_id in self._guild_ids)))

    async def edit(
        self,
        *,
        username: Absent[str] = MISSING,
        avatar: Absent[UPLOADABLE_TYPE] = MISSING,
        banner: Absent[UPLOADABLE_TYPE] = MISSING,
    ) -> None:
        """
        Edit the client's user.

        You can change the username, avatar, and banner, or any combination of the three.
        `avatar` and `banner` may be set to `None` to remove your bot's avatar/banner

        ??? Hint "Example Usage:"
            ```python
            await self.user.edit(avatar="path_to_file")
            ```
            or
            ```python
            await self.user.edit(username="hello world")
            ```

        Args:
            username: The username you want to use
            avatar: The avatar to use. Can be a image file, path, or `bytes` (see example)
            banner: The banner to use. Can be a image file, path, or `bytes`

        Raises:
            TooManyChanges: If you change the profile too many times

        """
        payload = {}
        if username:
            payload["username"] = username
        if avatar:
            payload["avatar"] = to_image_data(avatar)
        elif avatar is None:
            payload["avatar"] = None
        if banner:
            payload["banner"] = to_image_data(banner)
        elif banner is None:
            payload["banner"] = None

        try:
            resp = await self._client.http.modify_client_user(payload)
        except HTTPException:
            raise TooManyChanges(
                "You have changed your profile too frequently, you need to wait a while before trying again."
            ) from None
        if resp:
            self._client.cache.place_user_data(resp)

guilds: List[Guild] property

The guilds the user is in.

edit(*, username=MISSING, avatar=MISSING, banner=MISSING) async

Edit the client's user.

You can change the username, avatar, and banner, or any combination of the three. avatar and banner may be set to None to remove your bot's avatar/banner

Example Usage:

1
await self.user.edit(avatar="path_to_file")
or
1
await self.user.edit(username="hello world")

Parameters:

Name Type Description Default
username Absent[str]

The username you want to use

MISSING
avatar Absent[UPLOADABLE_TYPE]

The avatar to use. Can be a image file, path, or bytes (see example)

MISSING
banner Absent[UPLOADABLE_TYPE]

The banner to use. Can be a image file, path, or bytes

MISSING

Raises:

Type Description
TooManyChanges

If you change the profile too many times

Source code in interactions/models/discord/user.py
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
async def edit(
    self,
    *,
    username: Absent[str] = MISSING,
    avatar: Absent[UPLOADABLE_TYPE] = MISSING,
    banner: Absent[UPLOADABLE_TYPE] = MISSING,
) -> None:
    """
    Edit the client's user.

    You can change the username, avatar, and banner, or any combination of the three.
    `avatar` and `banner` may be set to `None` to remove your bot's avatar/banner

    ??? Hint "Example Usage:"
        ```python
        await self.user.edit(avatar="path_to_file")
        ```
        or
        ```python
        await self.user.edit(username="hello world")
        ```

    Args:
        username: The username you want to use
        avatar: The avatar to use. Can be a image file, path, or `bytes` (see example)
        banner: The banner to use. Can be a image file, path, or `bytes`

    Raises:
        TooManyChanges: If you change the profile too many times

    """
    payload = {}
    if username:
        payload["username"] = username
    if avatar:
        payload["avatar"] = to_image_data(avatar)
    elif avatar is None:
        payload["avatar"] = None
    if banner:
        payload["banner"] = to_image_data(banner)
    elif banner is None:
        payload["banner"] = None

    try:
        resp = await self._client.http.modify_client_user(payload)
    except HTTPException:
        raise TooManyChanges(
            "You have changed your profile too frequently, you need to wait a while before trying again."
        ) from None
    if resp:
        self._client.cache.place_user_data(resp)

Member

Bases: DiscordObject, _SendDMMixin

Source code in interactions/models/discord/user.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class Member(DiscordObject, _SendDMMixin):
    bot: bool = attrs.field(repr=True, default=False, metadata=docs("Is this user a bot?"))
    nick: Optional[str] = attrs.field(repr=True, default=None, metadata=docs("The user's nickname in this guild'"))
    deaf: bool = attrs.field(repr=False, default=False, metadata=docs("Has this user been deafened in voice channels?"))
    mute: bool = attrs.field(repr=False, default=False, metadata=docs("Has this user been muted in voice channels?"))
    flags: MemberFlags = attrs.field(
        repr=False, default=0, converter=MemberFlags, metadata=docs("The flags associated with this guild member")
    )
    joined_at: "Timestamp" = attrs.field(
        repr=False,
        default=MISSING,
        converter=optional(timestamp_converter),
        metadata=docs("When the user joined this guild"),
    )
    premium_since: Optional["Timestamp"] = attrs.field(
        default=None,
        converter=optional_c(timestamp_converter),
        metadata=docs("When the user started boosting the guild"),
    )
    pending: Optional[bool] = attrs.field(
        repr=False,
        default=None,
        metadata=docs("Whether the user has **not** passed guild's membership screening requirements"),
    )
    guild_avatar: "Asset" = attrs.field(repr=False, default=None, metadata=docs("The user's guild avatar"))
    communication_disabled_until: Optional["Timestamp"] = attrs.field(
        default=None,
        converter=optional_c(timestamp_converter),
        metadata=docs("When a member's timeout will expire, `None` or a time in the past if the user is not timed out"),
    )

    _guild_id: "Snowflake_Type" = attrs.field(repr=True, metadata=docs("The ID of the guild"))
    _role_ids: List["Snowflake_Type"] = attrs.field(
        repr=False,
        factory=list,
        converter=list_converter(to_snowflake),
        metadata=docs("The roles IDs this user has"),
    )

    _user_ref: frozenset = MISSING
    """A lookup reference to the user object"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if "user" in data:
            user_data = data.pop("user")
            client.cache.place_user_data(user_data)
            data["id"] = user_data["id"]
            data["bot"] = user_data.get("bot", False)
        elif "member" in data:
            member_data = data.pop("member")
            client.cache.place_user_data(data)
            member_data["id"] = data["id"]
            member_data["bot"] = data.get("bot", False)
            if "guild_id" not in member_data:
                member_data["guild_id"] = data.get("guild_id")
            data = member_data
        if data.get("avatar"):
            try:
                data["guild_avatar"] = Asset.from_path_hash(
                    client,
                    f"guilds/{data['guild_id']}/users/{data['id']}/avatars/{{}}",
                    data.pop("avatar", None),
                )
            except Exception as e:
                client.logger.warning(
                    f"[DEBUG NEEDED - REPORT THIS] Incomplete dictionary has been passed to member object: {e}"
                )
                raise

        data["role_ids"] = data.pop("roles", [])

        return data

    def update_from_dict(self, data) -> None:
        if "guild_id" not in data:
            data["guild_id"] = self._guild_id
        data["_role_ids"] = data.pop("roles", [])
        return super().update_from_dict(data)

    @property
    def user(self) -> "User":
        """Returns this member's user object."""
        return self._client.cache.get_user(self.id)

    def __str__(self) -> str:
        return self.user.tag

    def __getattr__(self, name: str) -> Any:
        # this allows for transparent access to user attributes
        if not hasattr(self.__class__._user_ref, "__iter__"):
            self.__class__._user_ref = frozenset(dir(User))

        if name in self.__class__._user_ref:
            return getattr(self.user, name)
        raise AttributeError(f"Neither `User` or `Member` have attribute {name}")

    @property
    def nickname(self) -> str:
        """Alias for nick."""
        return self.nick

    @nickname.setter
    def nickname(self, nickname: str) -> None:
        """Sets the member's nickname."""
        self.nick = nickname

    @property
    def guild(self) -> "Guild":
        """The guild object this member is from."""
        return self._client.cache.get_guild(self._guild_id)

    @property
    def roles(self) -> List["Role"]:
        """The roles this member has."""
        return [r for r in self.guild.roles if r.id in self._role_ids]

    @property
    def top_role(self) -> "Role":
        """The member's top most role."""
        return max(self.roles, key=lambda x: x.position) if self.roles else self.guild.default_role

    @property
    def display_name(self) -> str:
        """The users display name, will return nickname if one is set, otherwise will return username."""
        return self.nickname or self.user.global_name or self.user.username

    @property
    def display_avatar(self) -> "Asset":
        """The users displayed avatar, will return `guild_avatar` if one is set, otherwise will return user avatar."""
        return self.guild_avatar or self.user.avatar

    @property
    def avatar_url(self) -> str:
        """The users avatar url."""
        return self.display_avatar.url

    @property
    def premium(self) -> bool:
        """Is this member a server booster?"""
        return self.premium_since is not None

    @property
    def guild_permissions(self) -> Permissions:
        """
        Returns the permissions this member has in the guild.

        Returns:
            Permission data

        """
        guild = self.guild
        if guild.is_owner(self):
            return Permissions.ALL

        permissions = guild.default_role.permissions  # get @everyone role

        for role in self.roles:
            permissions |= role.permissions

        if Permissions.ADMINISTRATOR in permissions:
            return Permissions.ALL

        return permissions

    @property
    def voice(self) -> Optional["VoiceState"]:
        """Returns the voice state of this user if any."""
        return self._client.cache.get_voice_state(self.id)

    def has_permission(self, *permissions: Permissions) -> bool:
        """
        Checks if the member has all the given permission(s).

        ??? Hint "Example Usage:"
            Two different styles can be used to call this method.

            ```python
            member.has_permission(Permissions.KICK_MEMBERS, Permissions.BAN_MEMBERS)
            ```
            or
            ```python
            member.has_permission(Permissions.KICK_MEMBERS | Permissions.BAN_MEMBERS)
            ```

            If `member` has both permissions, `True` gets returned.

        Args:
            *permissions: The permission(s) to check whether the user has it.

        """
        # Get the user's permissions
        guild_permissions = self.guild_permissions

        return all(permission in guild_permissions for permission in permissions)

    def channel_permissions(self, channel: "TYPE_GUILD_CHANNEL") -> Permissions:
        """
        Returns the permissions this member has in a channel.

        Args:
            channel: The channel in question

        Returns:
            Permissions data

        ??? note
            This method is used in `Channel.permissions_for`

        """
        permissions = self.guild_permissions

        if Permissions.ADMINISTRATOR in permissions:
            return Permissions.ALL

        overwrites = tuple(
            filter(
                lambda overwrite: overwrite.id in (self._guild_id, self.id, *self._role_ids),
                channel.permission_overwrites,
            )
        )

        for everyone_overwrite in filter(lambda overwrite: overwrite.id == self._guild_id, overwrites):
            permissions &= ~everyone_overwrite.deny
            permissions |= everyone_overwrite.allow

        for role_overwrite in filter(lambda overwrite: overwrite.id not in (self._guild_id, self.id), overwrites):
            permissions &= ~role_overwrite.deny
            permissions |= role_overwrite.allow

        for member_overwrite in filter(lambda overwrite: overwrite.id == self.id, overwrites):
            permissions &= ~member_overwrite.deny
            permissions |= member_overwrite.allow

        return permissions

    async def edit_nickname(self, new_nickname: Absent[str] = MISSING, reason: Absent[str] = MISSING) -> None:
        """
        Change the user's nickname.

        Args:
            new_nickname: The new nickname to apply
            reason: The reason for this change

        !!! note
            Leave `new_nickname` empty to clean user's nickname

        """
        if self.id == self._client.user.id:
            await self._client.http.modify_current_member(self._guild_id, nickname=new_nickname, reason=reason)
        else:
            await self._client.http.modify_guild_member(self._guild_id, self.id, nickname=new_nickname, reason=reason)

    async def add_role(self, role: Union[Snowflake_Type, Role], reason: Absent[str] = MISSING) -> None:
        """
        Add a role to this member.

        Args:
            role: The role to add
            reason: The reason for adding this role

        """
        role = to_snowflake(role)
        await self._client.http.add_guild_member_role(self._guild_id, self.id, role, reason=reason)
        self._role_ids.append(role)

    async def add_roles(self, roles: Iterable[Union[Snowflake_Type, Role]], reason: Absent[str] = MISSING) -> None:
        """
        Atomically add multiple roles to this member.

        Args:
            roles: The roles to add
            reason: The reason for adding the roles

        """
        new_roles = set(self._role_ids) | {to_snowflake(r) for r in roles}
        await self.edit(roles=new_roles, reason=reason)

    async def remove_role(self, role: Union[Snowflake_Type, Role], reason: Absent[str] = MISSING) -> None:
        """
        Remove a role from this user.

        Args:
            role: The role to remove
            reason: The reason for this removal

        """
        role = to_snowflake(role)
        await self._client.http.remove_guild_member_role(self._guild_id, self.id, role, reason=reason)
        try:
            self._role_ids.remove(role)
        except ValueError:
            pass

    async def remove_roles(self, roles: Iterable[Union[Snowflake_Type, Role]], reason: Absent[str] = MISSING) -> None:
        """
        Atomically remove multiple roles from this member.

        Args:
            roles: The roles to remove
            reason: The reason for removing the roles

        """
        new_roles = set(self._role_ids) - {to_snowflake(r) for r in roles}
        await self.edit(roles=new_roles, reason=reason)

    def has_role(self, *roles: Union[Snowflake_Type, Role]) -> bool:
        """
        Checks if the user has the given role(s).

        Args:
            *roles: The role(s) to check whether the user has it.

        """
        return all(to_snowflake(role) in self._role_ids for role in roles)

    def has_any_role(self, roles: List[Union[Snowflake_Type, Role]]) -> bool:
        """
        Checks if the user has any of the given roles.

        Args:
            *roles: The Role(s) or role id(s) to check for

        """
        return any((self.has_role(to_snowflake(role)) for role in roles))

    async def timeout(
        self,
        communication_disabled_until: Union["Timestamp", datetime, int, float, str, None],
        reason: Absent[str] = MISSING,
    ) -> dict:
        """
        Disable a members communication for a given time.

        Args:
            communication_disabled_until: The time until the user can communicate again
            reason: The reason for this timeout

        """
        if isinstance(communication_disabled_until, (datetime, int, float, str)):
            communication_disabled_until = timestamp_converter(communication_disabled_until)

        self.communication_disabled_until = communication_disabled_until

        return await self._client.http.modify_guild_member(
            self._guild_id,
            self.id,
            communication_disabled_until=communication_disabled_until,
            reason=reason,
        )

    async def move(self, channel_id: "Snowflake_Type") -> None:
        """
        Moves the member to a different voice channel.

        Args:
            channel_id: The voice channel to move the member to

        """
        await self._client.http.modify_guild_member(self._guild_id, self.id, channel_id=channel_id)

    async def disconnect(self) -> None:
        """Disconnects the member from the voice channel."""
        await self._client.http.modify_guild_member(self._guild_id, self.id, channel_id=None)

    async def edit(
        self,
        *,
        nickname: Absent[str] = MISSING,
        roles: Absent[Iterable["Snowflake_Type"]] = MISSING,
        mute: Absent[bool] = MISSING,
        deaf: Absent[bool] = MISSING,
        channel_id: Absent["Snowflake_Type"] = MISSING,
        communication_disabled_until: Absent[Union["Timestamp", None]] = MISSING,
        flags: Absent[int] = MISSING,
        reason: Absent[str] = MISSING,
    ) -> None:
        """
        Modify attrbutes of this guild member.

        Args:
            nickname: Value to set users nickname to
            roles: Array of role ids the member is assigned
            mute: Whether the user is muted in voice channels. Will throw a 400 if the user is not in a voice channel
            deaf: Whether the user is deafened in voice channels
            channel_id: id of channel to move user to (if they are connected to voice)
            communication_disabled_until: 	when the user's timeout will expire and the user will be able to communicate in the guild again
            flags: Represents the guild member flags
            reason: An optional reason for the audit log

        """
        await self._client.http.modify_guild_member(
            self._guild_id,
            self.id,
            nickname=nickname,
            roles=roles,
            mute=mute,
            deaf=deaf,
            channel_id=channel_id,
            communication_disabled_until=communication_disabled_until,
            flags=flags,
            reason=reason,
        )

    async def kick(self, reason: Absent[str] = MISSING) -> None:
        """
        Remove a member from the guild.

        Args:
            reason: The reason for this removal

        """
        await self._client.http.remove_guild_member(self._guild_id, self.id, reason=reason)

    async def ban(
        self,
        delete_message_days: Absent[int] = MISSING,
        delete_message_seconds: int = 0,
        reason: Absent[str] = MISSING,
    ) -> None:
        """
        Ban a member from the guild.

        Args:
            delete_message_days: (deprecated) The number of days of messages to delete
            delete_message_seconds: The number of seconds of messages to delete
            reason: The reason for this ban

        """
        if delete_message_days is not MISSING:
            warn(
                "delete_message_days  is deprecated and will be removed in a future update",
                DeprecationWarning,
                stacklevel=2,
            )
            delete_message_seconds = delete_message_days * 3600
        await self._client.http.create_guild_ban(self._guild_id, self.id, delete_message_seconds, reason=reason)

avatar_url: str property

The users avatar url.

display_avatar: Asset property

The users displayed avatar, will return guild_avatar if one is set, otherwise will return user avatar.

display_name: str property

The users display name, will return nickname if one is set, otherwise will return username.

guild: Guild property

The guild object this member is from.

guild_permissions: Permissions property

Returns the permissions this member has in the guild.

Returns:

Type Description
Permissions

Permission data

nickname: str writable property

Alias for nick.

premium: bool property

Is this member a server booster?

roles: List[Role] property

The roles this member has.

top_role: Role property

The member's top most role.

user: User property

Returns this member's user object.

voice: Optional[VoiceState] property

Returns the voice state of this user if any.

add_role(role, reason=MISSING) async

Add a role to this member.

Parameters:

Name Type Description Default
role Union[Snowflake_Type, Role]

The role to add

required
reason Absent[str]

The reason for adding this role

MISSING
Source code in interactions/models/discord/user.py
549
550
551
552
553
554
555
556
557
558
559
560
async def add_role(self, role: Union[Snowflake_Type, Role], reason: Absent[str] = MISSING) -> None:
    """
    Add a role to this member.

    Args:
        role: The role to add
        reason: The reason for adding this role

    """
    role = to_snowflake(role)
    await self._client.http.add_guild_member_role(self._guild_id, self.id, role, reason=reason)
    self._role_ids.append(role)

add_roles(roles, reason=MISSING) async

Atomically add multiple roles to this member.

Parameters:

Name Type Description Default
roles Iterable[Union[Snowflake_Type, Role]]

The roles to add

required
reason Absent[str]

The reason for adding the roles

MISSING
Source code in interactions/models/discord/user.py
562
563
564
565
566
567
568
569
570
571
572
async def add_roles(self, roles: Iterable[Union[Snowflake_Type, Role]], reason: Absent[str] = MISSING) -> None:
    """
    Atomically add multiple roles to this member.

    Args:
        roles: The roles to add
        reason: The reason for adding the roles

    """
    new_roles = set(self._role_ids) | {to_snowflake(r) for r in roles}
    await self.edit(roles=new_roles, reason=reason)

ban(delete_message_days=MISSING, delete_message_seconds=0, reason=MISSING) async

Ban a member from the guild.

Parameters:

Name Type Description Default
delete_message_days Absent[int]

(deprecated) The number of days of messages to delete

MISSING
delete_message_seconds int

The number of seconds of messages to delete

0
reason Absent[str]

The reason for this ban

MISSING
Source code in interactions/models/discord/user.py
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
async def ban(
    self,
    delete_message_days: Absent[int] = MISSING,
    delete_message_seconds: int = 0,
    reason: Absent[str] = MISSING,
) -> None:
    """
    Ban a member from the guild.

    Args:
        delete_message_days: (deprecated) The number of days of messages to delete
        delete_message_seconds: The number of seconds of messages to delete
        reason: The reason for this ban

    """
    if delete_message_days is not MISSING:
        warn(
            "delete_message_days  is deprecated and will be removed in a future update",
            DeprecationWarning,
            stacklevel=2,
        )
        delete_message_seconds = delete_message_days * 3600
    await self._client.http.create_guild_ban(self._guild_id, self.id, delete_message_seconds, reason=reason)

channel_permissions(channel)

Returns the permissions this member has in a channel.

Parameters:

Name Type Description Default
channel TYPE_GUILD_CHANNEL

The channel in question

required

Returns:

Type Description
Permissions

Permissions data

Note

This method is used in Channel.permissions_for

Source code in interactions/models/discord/user.py
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
def channel_permissions(self, channel: "TYPE_GUILD_CHANNEL") -> Permissions:
    """
    Returns the permissions this member has in a channel.

    Args:
        channel: The channel in question

    Returns:
        Permissions data

    ??? note
        This method is used in `Channel.permissions_for`

    """
    permissions = self.guild_permissions

    if Permissions.ADMINISTRATOR in permissions:
        return Permissions.ALL

    overwrites = tuple(
        filter(
            lambda overwrite: overwrite.id in (self._guild_id, self.id, *self._role_ids),
            channel.permission_overwrites,
        )
    )

    for everyone_overwrite in filter(lambda overwrite: overwrite.id == self._guild_id, overwrites):
        permissions &= ~everyone_overwrite.deny
        permissions |= everyone_overwrite.allow

    for role_overwrite in filter(lambda overwrite: overwrite.id not in (self._guild_id, self.id), overwrites):
        permissions &= ~role_overwrite.deny
        permissions |= role_overwrite.allow

    for member_overwrite in filter(lambda overwrite: overwrite.id == self.id, overwrites):
        permissions &= ~member_overwrite.deny
        permissions |= member_overwrite.allow

    return permissions

disconnect() async

Disconnects the member from the voice channel.

Source code in interactions/models/discord/user.py
657
658
659
async def disconnect(self) -> None:
    """Disconnects the member from the voice channel."""
    await self._client.http.modify_guild_member(self._guild_id, self.id, channel_id=None)

edit(*, nickname=MISSING, roles=MISSING, mute=MISSING, deaf=MISSING, channel_id=MISSING, communication_disabled_until=MISSING, flags=MISSING, reason=MISSING) async

Modify attrbutes of this guild member.

Parameters:

Name Type Description Default
nickname Absent[str]

Value to set users nickname to

MISSING
roles Absent[Iterable[Snowflake_Type]]

Array of role ids the member is assigned

MISSING
mute Absent[bool]

Whether the user is muted in voice channels. Will throw a 400 if the user is not in a voice channel

MISSING
deaf Absent[bool]

Whether the user is deafened in voice channels

MISSING
channel_id Absent[Snowflake_Type]

id of channel to move user to (if they are connected to voice)

MISSING
communication_disabled_until Absent[Union[Timestamp, None]]

when the user's timeout will expire and the user will be able to communicate in the guild again

MISSING
flags Absent[int]

Represents the guild member flags

MISSING
reason Absent[str]

An optional reason for the audit log

MISSING
Source code in interactions/models/discord/user.py
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
async def edit(
    self,
    *,
    nickname: Absent[str] = MISSING,
    roles: Absent[Iterable["Snowflake_Type"]] = MISSING,
    mute: Absent[bool] = MISSING,
    deaf: Absent[bool] = MISSING,
    channel_id: Absent["Snowflake_Type"] = MISSING,
    communication_disabled_until: Absent[Union["Timestamp", None]] = MISSING,
    flags: Absent[int] = MISSING,
    reason: Absent[str] = MISSING,
) -> None:
    """
    Modify attrbutes of this guild member.

    Args:
        nickname: Value to set users nickname to
        roles: Array of role ids the member is assigned
        mute: Whether the user is muted in voice channels. Will throw a 400 if the user is not in a voice channel
        deaf: Whether the user is deafened in voice channels
        channel_id: id of channel to move user to (if they are connected to voice)
        communication_disabled_until: 	when the user's timeout will expire and the user will be able to communicate in the guild again
        flags: Represents the guild member flags
        reason: An optional reason for the audit log

    """
    await self._client.http.modify_guild_member(
        self._guild_id,
        self.id,
        nickname=nickname,
        roles=roles,
        mute=mute,
        deaf=deaf,
        channel_id=channel_id,
        communication_disabled_until=communication_disabled_until,
        flags=flags,
        reason=reason,
    )

edit_nickname(new_nickname=MISSING, reason=MISSING) async

Change the user's nickname.

Parameters:

Name Type Description Default
new_nickname Absent[str]

The new nickname to apply

MISSING
reason Absent[str]

The reason for this change

MISSING

Note

Leave new_nickname empty to clean user's nickname

Source code in interactions/models/discord/user.py
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
async def edit_nickname(self, new_nickname: Absent[str] = MISSING, reason: Absent[str] = MISSING) -> None:
    """
    Change the user's nickname.

    Args:
        new_nickname: The new nickname to apply
        reason: The reason for this change

    !!! note
        Leave `new_nickname` empty to clean user's nickname

    """
    if self.id == self._client.user.id:
        await self._client.http.modify_current_member(self._guild_id, nickname=new_nickname, reason=reason)
    else:
        await self._client.http.modify_guild_member(self._guild_id, self.id, nickname=new_nickname, reason=reason)

has_any_role(roles)

Checks if the user has any of the given roles.

Parameters:

Name Type Description Default
*roles List[Union[Snowflake_Type, Role]]

The Role(s) or role id(s) to check for

required
Source code in interactions/models/discord/user.py
612
613
614
615
616
617
618
619
620
def has_any_role(self, roles: List[Union[Snowflake_Type, Role]]) -> bool:
    """
    Checks if the user has any of the given roles.

    Args:
        *roles: The Role(s) or role id(s) to check for

    """
    return any((self.has_role(to_snowflake(role)) for role in roles))

has_permission(*permissions)

Checks if the member has all the given permission(s).

Example Usage:

Two different styles can be used to call this method.

1
member.has_permission(Permissions.KICK_MEMBERS, Permissions.BAN_MEMBERS)
or
1
member.has_permission(Permissions.KICK_MEMBERS | Permissions.BAN_MEMBERS)

If member has both permissions, True gets returned.

Parameters:

Name Type Description Default
*permissions Permissions

The permission(s) to check whether the user has it.

()
Source code in interactions/models/discord/user.py
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
def has_permission(self, *permissions: Permissions) -> bool:
    """
    Checks if the member has all the given permission(s).

    ??? Hint "Example Usage:"
        Two different styles can be used to call this method.

        ```python
        member.has_permission(Permissions.KICK_MEMBERS, Permissions.BAN_MEMBERS)
        ```
        or
        ```python
        member.has_permission(Permissions.KICK_MEMBERS | Permissions.BAN_MEMBERS)
        ```

        If `member` has both permissions, `True` gets returned.

    Args:
        *permissions: The permission(s) to check whether the user has it.

    """
    # Get the user's permissions
    guild_permissions = self.guild_permissions

    return all(permission in guild_permissions for permission in permissions)

has_role(*roles)

Checks if the user has the given role(s).

Parameters:

Name Type Description Default
*roles Union[Snowflake_Type, Role]

The role(s) to check whether the user has it.

()
Source code in interactions/models/discord/user.py
602
603
604
605
606
607
608
609
610
def has_role(self, *roles: Union[Snowflake_Type, Role]) -> bool:
    """
    Checks if the user has the given role(s).

    Args:
        *roles: The role(s) to check whether the user has it.

    """
    return all(to_snowflake(role) in self._role_ids for role in roles)

kick(reason=MISSING) async

Remove a member from the guild.

Parameters:

Name Type Description Default
reason Absent[str]

The reason for this removal

MISSING
Source code in interactions/models/discord/user.py
700
701
702
703
704
705
706
707
708
async def kick(self, reason: Absent[str] = MISSING) -> None:
    """
    Remove a member from the guild.

    Args:
        reason: The reason for this removal

    """
    await self._client.http.remove_guild_member(self._guild_id, self.id, reason=reason)

move(channel_id) async

Moves the member to a different voice channel.

Parameters:

Name Type Description Default
channel_id Snowflake_Type

The voice channel to move the member to

required
Source code in interactions/models/discord/user.py
647
648
649
650
651
652
653
654
655
async def move(self, channel_id: "Snowflake_Type") -> None:
    """
    Moves the member to a different voice channel.

    Args:
        channel_id: The voice channel to move the member to

    """
    await self._client.http.modify_guild_member(self._guild_id, self.id, channel_id=channel_id)

remove_role(role, reason=MISSING) async

Remove a role from this user.

Parameters:

Name Type Description Default
role Union[Snowflake_Type, Role]

The role to remove

required
reason Absent[str]

The reason for this removal

MISSING
Source code in interactions/models/discord/user.py
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
async def remove_role(self, role: Union[Snowflake_Type, Role], reason: Absent[str] = MISSING) -> None:
    """
    Remove a role from this user.

    Args:
        role: The role to remove
        reason: The reason for this removal

    """
    role = to_snowflake(role)
    await self._client.http.remove_guild_member_role(self._guild_id, self.id, role, reason=reason)
    try:
        self._role_ids.remove(role)
    except ValueError:
        pass

remove_roles(roles, reason=MISSING) async

Atomically remove multiple roles from this member.

Parameters:

Name Type Description Default
roles Iterable[Union[Snowflake_Type, Role]]

The roles to remove

required
reason Absent[str]

The reason for removing the roles

MISSING
Source code in interactions/models/discord/user.py
590
591
592
593
594
595
596
597
598
599
600
async def remove_roles(self, roles: Iterable[Union[Snowflake_Type, Role]], reason: Absent[str] = MISSING) -> None:
    """
    Atomically remove multiple roles from this member.

    Args:
        roles: The roles to remove
        reason: The reason for removing the roles

    """
    new_roles = set(self._role_ids) - {to_snowflake(r) for r in roles}
    await self.edit(roles=new_roles, reason=reason)

timeout(communication_disabled_until, reason=MISSING) async

Disable a members communication for a given time.

Parameters:

Name Type Description Default
communication_disabled_until Union[Timestamp, datetime, int, float, str, None]

The time until the user can communicate again

required
reason Absent[str]

The reason for this timeout

MISSING
Source code in interactions/models/discord/user.py
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
async def timeout(
    self,
    communication_disabled_until: Union["Timestamp", datetime, int, float, str, None],
    reason: Absent[str] = MISSING,
) -> dict:
    """
    Disable a members communication for a given time.

    Args:
        communication_disabled_until: The time until the user can communicate again
        reason: The reason for this timeout

    """
    if isinstance(communication_disabled_until, (datetime, int, float, str)):
        communication_disabled_until = timestamp_converter(communication_disabled_until)

    self.communication_disabled_until = communication_disabled_until

    return await self._client.http.modify_guild_member(
        self._guild_id,
        self.id,
        communication_disabled_until=communication_disabled_until,
        reason=reason,
    )

User

Bases: BaseUser

Source code in interactions/models/discord/user.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class User(BaseUser):
    bot: bool = attrs.field(repr=True, default=False, metadata=docs("Is this user a bot?"))
    system: bool = attrs.field(
        default=False,
        metadata=docs("whether the user is an Official Discord System user (part of the urgent message system)"),
    )
    public_flags: "UserFlags" = attrs.field(
        repr=True,
        default=0,
        converter=UserFlags,
        metadata=docs("The flags associated with this user"),
    )
    premium_type: "PremiumType" = attrs.field(
        repr=False,
        default=0,
        converter=PremiumType,
        metadata=docs("The type of nitro subscription on a user's account"),
    )

    banner: Optional["Asset"] = attrs.field(repr=False, default=None, metadata=docs("The user's banner"))
    avatar_decoration: Optional["Asset"] = attrs.field(
        repr=False, default=None, metadata=docs("The user's avatar decoration")
    )
    accent_color: Optional["Color"] = attrs.field(
        default=None,
        converter=optional_c(Color),
        metadata=docs("The user's banner color"),
    )
    activities: list[Activity] = attrs.field(
        factory=list,
        converter=list_converter(optional(Activity.from_dict)),
        metadata=docs("A list of activities the user is in"),
    )
    status: Absent[Status] = attrs.field(
        repr=False, default=MISSING, metadata=docs("The user's status"), converter=optional(Status)
    )

    _fetched: bool = attrs.field(repr=False, default=False, metadata=docs("Has the user been fetched?"))
    """Flag to indicate if a `fetch` api call has been made to get the full user object."""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data = super()._process_dict(data, client)
        if any(field in data for field in ("banner", "accent_color", "avatar_decoration")):
            data["_fetched"] = True

        if "banner" in data:
            data["banner"] = Asset.from_path_hash(client, f"banners/{data['id']}/{{}}", data["banner"])

        if data.get("premium_type", None) is None:
            data["premium_type"] = 0

        if data.get("avatar_decoration", None):
            data["avatar_decoration"] = Asset.from_path_hash(
                client, "avatar-decoration-presets/{}", data["avatar_decoration"]
            )

        return data

    @property
    def member_instances(self) -> List["Member"]:
        """
        Returns the member object for all guilds both the bot and the user are in.

        !!! note
            This will only be accurate if the guild members are cached internally
        """
        member_objs = [
            self._client.cache.get_member(guild_id=guild.id, user_id=self.id) for guild in self._client.guilds
        ]
        return [member for member in member_objs if member]

member_instances: List[Member] property

Returns the member object for all guilds both the bot and the user are in.

Note

This will only be accurate if the guild members are cached internally

VoiceRegion

Bases: DictSerializationMixin

A voice region.

Source code in interactions/models/discord/voice_state.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class VoiceRegion(DictSerializationMixin):
    """A voice region."""

    id: str = attrs.field(repr=True)
    """unique ID for the region"""
    name: str = attrs.field(repr=True)
    """name of the region"""
    vip: bool = attrs.field(default=False, repr=True)
    """whether this is a VIP-only voice region"""
    optimal: bool = attrs.field(repr=False, default=False)
    """true for a single server that is closest to the current user's client"""
    deprecated: bool = attrs.field(repr=False, default=False)
    """whether this is a deprecated voice region (avoid switching to these)"""
    custom: bool = attrs.field(repr=False, default=False)
    """whether this is a custom voice region (used for events/etc)"""

    def __str__(self) -> str:
        return self.name

custom: bool = attrs.field(repr=False, default=False) class-attribute

whether this is a custom voice region (used for events/etc)

deprecated: bool = attrs.field(repr=False, default=False) class-attribute

whether this is a deprecated voice region (avoid switching to these)

id: str = attrs.field(repr=True) class-attribute

unique ID for the region

name: str = attrs.field(repr=True) class-attribute

name of the region

optimal: bool = attrs.field(repr=False, default=False) class-attribute

true for a single server that is closest to the current user's client

vip: bool = attrs.field(default=False, repr=True) class-attribute

whether this is a VIP-only voice region

VoiceState

Bases: ClientObject

Source code in interactions/models/discord/voice_state.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class VoiceState(ClientObject):
    user_id: "Snowflake_Type" = attrs.field(repr=False, default=MISSING, converter=to_snowflake)
    """the user id this voice state is for"""
    session_id: str = attrs.field(repr=False, default=MISSING)
    """the session id for this voice state"""
    deaf: bool = attrs.field(repr=False, default=False)
    """whether this user is deafened by the server"""
    mute: bool = attrs.field(repr=False, default=False)
    """whether this user is muted by the server"""
    self_deaf: bool = attrs.field(repr=False, default=False)
    """whether this user is locally deafened"""
    self_mute: bool = attrs.field(repr=False, default=False)
    """whether this user is locally muted"""
    self_stream: Optional[bool] = attrs.field(repr=False, default=False)
    """whether this user is streaming using "Go Live\""""
    self_video: bool = attrs.field(repr=False, default=False)
    """whether this user's camera is enabled"""
    suppress: bool = attrs.field(repr=False, default=False)
    """whether this user is muted by the current user"""
    request_to_speak_timestamp: Optional[Timestamp] = attrs.field(
        repr=False, default=None, converter=optional_c(timestamp_converter)
    )
    """the time at which the user requested to speak"""

    # internal for props
    _guild_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None, converter=to_snowflake)
    _channel_id: "Snowflake_Type" = attrs.field(repr=False, converter=to_snowflake)
    _member_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None, converter=to_snowflake)

    @property
    def guild(self) -> "Guild":
        """The guild this voice state is for."""
        return self._client.cache.get_guild(self._guild_id) if self._guild_id else None

    @property
    def channel(self) -> "TYPE_VOICE_CHANNEL":
        """The channel the user is connected to."""
        channel: "TYPE_VOICE_CHANNEL" = self._client.cache.get_channel(self._channel_id)

        if channel and self._member_id not in channel._voice_member_ids:
            # the list of voice members need to be deepcopied, otherwise the cached obj will be updated
            # noinspection PyProtectedMember
            voice_member_ids = copy.deepcopy(channel._voice_member_ids)

            # create a copy of the obj
            channel = copy.copy(channel)
            channel._voice_member_ids = voice_member_ids

            # add the member to that list
            # noinspection PyProtectedMember
            channel._voice_member_ids.append(self._member_id)

        return channel

    @property
    def member(self) -> "Member":
        """The member this voice state is for."""
        return self._client.cache.get_member(self._guild_id, self._member_id) if self._guild_id else None

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if member := data.pop("member", None):
            member = client.cache.place_member_data(data["guild_id"], member)
            data["member_id"] = member.id
        else:
            data["member_id"] = data["user_id"]
        return data

channel: TYPE_VOICE_CHANNEL property

The channel the user is connected to.

deaf: bool = attrs.field(repr=False, default=False) class-attribute

whether this user is deafened by the server

guild: Guild property

The guild this voice state is for.

member: Member property

The member this voice state is for.

mute: bool = attrs.field(repr=False, default=False) class-attribute

whether this user is muted by the server

request_to_speak_timestamp: Optional[Timestamp] = attrs.field(repr=False, default=None, converter=optional_c(timestamp_converter)) class-attribute

the time at which the user requested to speak

self_deaf: bool = attrs.field(repr=False, default=False) class-attribute

whether this user is locally deafened

self_mute: bool = attrs.field(repr=False, default=False) class-attribute

whether this user is locally muted

self_stream: Optional[bool] = attrs.field(repr=False, default=False) class-attribute

whether this user is streaming using "Go Live"

self_video: bool = attrs.field(repr=False, default=False) class-attribute

whether this user's camera is enabled

session_id: str = attrs.field(repr=False, default=MISSING) class-attribute

the session id for this voice state

suppress: bool = attrs.field(repr=False, default=False) class-attribute

whether this user is muted by the current user

user_id: Snowflake_Type = attrs.field(repr=False, default=MISSING, converter=to_snowflake) class-attribute

the user id this voice state is for


Guild

AuditLog

Bases: ClientObject

Contains entries and other data given from selected

Source code in interactions/models/discord/guild.py
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class AuditLog(ClientObject):
    """Contains entries and other data given from selected"""

    application_commands: list["InteractionCommand"] = attrs.field(
        repr=False, factory=list, converter=optional(deserialize_app_cmds)
    )
    """list of application commands that have had their permissions updated"""
    entries: Optional[List["AuditLogEntry"]] = attrs.field(repr=False, default=MISSING)
    """list of audit log entries"""
    scheduled_events: Optional[List["models.ScheduledEvent"]] = attrs.field(repr=False, default=MISSING)
    """list of guild scheduled events found in the audit log"""
    integrations: Optional[List["GuildIntegration"]] = attrs.field(repr=False, default=MISSING)
    """list of partial integration objects"""
    threads: Optional[List["models.ThreadChannel"]] = attrs.field(repr=False, default=MISSING)
    """list of threads found in the audit log"""
    users: Optional[List["models.User"]] = attrs.field(repr=False, default=MISSING)
    """list of users found in the audit log"""
    webhooks: Optional[List["models.Webhook"]] = attrs.field(repr=False, default=MISSING)
    """list of webhooks found in the audit log"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if entries := data.get("audit_log_entries", None):
            data["entries"] = AuditLogEntry.from_list(entries, client)
        if scheduled_events := data.get("guild_scheduled_events", None):
            data["scheduled_events"] = models.ScheduledEvent.from_list(scheduled_events, client)
        if integrations := data.get("integrations", None):
            data["integrations"] = GuildIntegration.from_list(integrations, client)
        if threads := data.get("threads", None):
            data["threads"] = models.ThreadChannel.from_list(threads, client)
        if users := data.get("users", None):
            data["users"] = models.User.from_list(users, client)
        if webhooks := data.get("webhooks", None):
            data["webhooks"] = models.Webhook.from_list(webhooks, client)

        return data

application_commands: list[InteractionCommand] = attrs.field(repr=False, factory=list, converter=optional(deserialize_app_cmds)) class-attribute

list of application commands that have had their permissions updated

entries: Optional[List[AuditLogEntry]] = attrs.field(repr=False, default=MISSING) class-attribute

list of audit log entries

integrations: Optional[List[GuildIntegration]] = attrs.field(repr=False, default=MISSING) class-attribute

list of partial integration objects

scheduled_events: Optional[List[models.ScheduledEvent]] = attrs.field(repr=False, default=MISSING) class-attribute

list of guild scheduled events found in the audit log

threads: Optional[List[models.ThreadChannel]] = attrs.field(repr=False, default=MISSING) class-attribute

list of threads found in the audit log

users: Optional[List[models.User]] = attrs.field(repr=False, default=MISSING) class-attribute

list of users found in the audit log

webhooks: Optional[List[models.Webhook]] = attrs.field(repr=False, default=MISSING) class-attribute

list of webhooks found in the audit log

AuditLogChange

Bases: ClientObject

Source code in interactions/models/discord/guild.py
2383
2384
2385
2386
2387
2388
2389
2390
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class AuditLogChange(ClientObject):
    key: str = attrs.field(repr=True)
    """name of audit log change key"""
    new_value: Optional[Union[list, str, int, bool, "Snowflake_Type"]] = attrs.field(repr=False, default=MISSING)
    """new value of the key"""
    old_value: Optional[Union[list, str, int, bool, "Snowflake_Type"]] = attrs.field(repr=False, default=MISSING)
    """old value of the key"""

key: str = attrs.field(repr=True) class-attribute

name of audit log change key

new_value: Optional[Union[list, str, int, bool, Snowflake_Type]] = attrs.field(repr=False, default=MISSING) class-attribute

new value of the key

old_value: Optional[Union[list, str, int, bool, Snowflake_Type]] = attrs.field(repr=False, default=MISSING) class-attribute

old value of the key

AuditLogEntry

Bases: DiscordObject

Source code in interactions/models/discord/guild.py
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class AuditLogEntry(DiscordObject):
    target_id: Optional["Snowflake_Type"] = attrs.field(repr=False, converter=optional(to_snowflake))
    """id of the affected entity (webhook, user, role, etc.)"""
    user_id: "Snowflake_Type" = attrs.field(repr=False, converter=optional(to_snowflake))
    """the user who made the changes"""
    action_type: "AuditLogEventType" = attrs.field(repr=False, converter=AuditLogEventType)
    """type of action that occurred"""
    changes: Optional[List[AuditLogChange]] = attrs.field(repr=False, default=MISSING)
    """changes made to the target_id"""
    options: Optional[Union["Snowflake_Type", str]] = attrs.field(repr=False, default=MISSING)
    """additional info for certain action types"""
    reason: Optional[str] = attrs.field(repr=False, default=MISSING)
    """the reason for the change (0-512 characters)"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if changes := data.get("changes", None):
            data["changes"] = AuditLogChange.from_list(changes, client)

        return data

action_type: AuditLogEventType = attrs.field(repr=False, converter=AuditLogEventType) class-attribute

type of action that occurred

changes: Optional[List[AuditLogChange]] = attrs.field(repr=False, default=MISSING) class-attribute

changes made to the target_id

options: Optional[Union[Snowflake_Type, str]] = attrs.field(repr=False, default=MISSING) class-attribute

additional info for certain action types

reason: Optional[str] = attrs.field(repr=False, default=MISSING) class-attribute

the reason for the change (0-512 characters)

target_id: Optional[Snowflake_Type] = attrs.field(repr=False, converter=optional(to_snowflake)) class-attribute

id of the affected entity (webhook, user, role, etc.)

user_id: Snowflake_Type = attrs.field(repr=False, converter=optional(to_snowflake)) class-attribute

the user who made the changes

AuditLogHistory

Bases: AsyncIterator

An async iterator for searching through a audit log's entry history.

Attributes:

Name Type Description
guild
user_id
action_type
before Snowflake_Type

get messages before this message ID

after Snowflake_Type

get messages after this message ID

limit Snowflake_Type

The maximum number of entries to return (set to 0 for no limit)

Source code in interactions/models/discord/guild.py
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
class AuditLogHistory(AsyncIterator):
    """
    An async iterator for searching through a audit log's entry history.

    Attributes:
        guild (:class:`Guild`): The guild to search through.
        user_id (:class:`Snowflake_Type`): The user ID to search for.
        action_type (:class:`AuditLogEventType`): The action type to search for.
        before: get messages before this message ID
        after: get messages after this message ID
        limit: The maximum number of entries to return (set to 0 for no limit)

    """

    def __init__(
        self,
        guild: "Guild",
        user_id: Snowflake_Type = None,
        action_type: "AuditLogEventType" = None,
        before: Snowflake_Type = None,
        after: Snowflake_Type = None,
        limit: int = 50,
    ) -> None:
        self.guild: "Guild" = guild
        self.user_id: Snowflake_Type = user_id
        self.action_type: "AuditLogEventType" = action_type
        self.before: Snowflake_Type = before
        self.after: Snowflake_Type = after
        super().__init__(limit)

    async def fetch(self) -> List["AuditLog"]:
        """
        Retrieves the audit log entries from discord API.

        Returns:
            The list of audit log entries.

        """
        if self.after:
            if not self.last:
                self.last = namedtuple("temp", "id")
                self.last.id = self.after
            log = await self.guild.fetch_audit_log(limit=self.get_limit, after=self.last.id)
        else:
            if self.before and not self.last:
                self.last = namedtuple("temp", "id")
                self.last.id = self.before

            log = await self.guild.fetch_audit_log(limit=self.get_limit, before=self.last.id)
        entries = log.entries or []

        return entries

fetch() async

Retrieves the audit log entries from discord API.

Returns:

Type Description
List[AuditLog]

The list of audit log entries.

Source code in interactions/models/discord/guild.py
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
async def fetch(self) -> List["AuditLog"]:
    """
    Retrieves the audit log entries from discord API.

    Returns:
        The list of audit log entries.

    """
    if self.after:
        if not self.last:
            self.last = namedtuple("temp", "id")
            self.last.id = self.after
        log = await self.guild.fetch_audit_log(limit=self.get_limit, after=self.last.id)
    else:
        if self.before and not self.last:
            self.last = namedtuple("temp", "id")
            self.last.id = self.before

        log = await self.guild.fetch_audit_log(limit=self.get_limit, before=self.last.id)
    entries = log.entries or []

    return entries

BaseGuild

Bases: DiscordObject

Source code in interactions/models/discord/guild.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class BaseGuild(DiscordObject):
    name: str = attrs.field(repr=True)
    """Name of guild. (2-100 characters, excluding trailing and leading whitespace)"""
    description: Optional[str] = attrs.field(repr=True, default=None)
    """The description for the guild, if the guild is discoverable"""

    icon: Optional["models.Asset"] = attrs.field(repr=False, default=None)
    """Icon image asset"""
    splash: Optional["models.Asset"] = attrs.field(repr=False, default=None)
    """Splash image asset"""
    discovery_splash: Optional["models.Asset"] = attrs.field(repr=False, default=None)
    """Discovery splash image. Only present for guilds with the "DISCOVERABLE" feature."""
    features: List[str] = attrs.field(repr=False, factory=list)
    """The features of this guild"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if icon_hash := data.pop("icon", None):
            data["icon"] = models.Asset.from_path_hash(client, f"icons/{data['id']}/{{}}", icon_hash)
        if splash_hash := data.pop("splash", None):
            data["splash"] = models.Asset.from_path_hash(client, f"splashes/{data['id']}/{{}}", splash_hash)
        if disco_splash := data.pop("discovery_splash", None):
            data["discovery_splash"] = models.Asset.from_path_hash(
                client, f"discovery-splashes/{data['id']}/{{}}", disco_splash
            )
        return data

description: Optional[str] = attrs.field(repr=True, default=None) class-attribute

The description for the guild, if the guild is discoverable

discovery_splash: Optional[models.Asset] = attrs.field(repr=False, default=None) class-attribute

Discovery splash image. Only present for guilds with the "DISCOVERABLE" feature.

features: List[str] = attrs.field(repr=False, factory=list) class-attribute

The features of this guild

icon: Optional[models.Asset] = attrs.field(repr=False, default=None) class-attribute

Icon image asset

name: str = attrs.field(repr=True) class-attribute

Name of guild. (2-100 characters, excluding trailing and leading whitespace)

splash: Optional[models.Asset] = attrs.field(repr=False, default=None) class-attribute

Splash image asset

BulkBanResponse

Bases: ClientObject

Source code in interactions/models/discord/guild.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class BulkBanResponse(ClientObject):
    _banned_users: list[Snowflake_Type] = attrs.field(repr=False, converter=to_snowflake_list)
    """List of user IDs that were successfully banned."""
    _failed_users: list[Snowflake_Type] = attrs.field(repr=False, converter=to_snowflake_list)
    """List of user IDs that were not banned."""

    @property
    def banned_users(self) -> List["models.User | None"]:
        """List of users that were successfully banned."""
        return [self.client.cache.get_user(u_id) for u_id in self._banned_users]

    @property
    def failed_users(self) -> List["models.User | None"]:
        """List of users that were not banned."""
        return [self.client.cache.get_user(u_id) for u_id in self._failed_users]

    @property
    def failed_user_ids(self) -> List[Snowflake_Type]:
        """List of user IDs that were not banned."""
        return self._failed_users

    @property
    def banned_user_ids(self) -> List[Snowflake_Type]:
        """List of user IDs that were successfully banned."""
        return self._banned_users

banned_user_ids: List[Snowflake_Type] property

List of user IDs that were successfully banned.

banned_users: List[models.User | None] property

List of users that were successfully banned.

failed_user_ids: List[Snowflake_Type] property

List of user IDs that were not banned.

failed_users: List[models.User | None] property

List of users that were not banned.

Guild

Bases: BaseGuild

Guilds in Discord represent an isolated collection of users and channels, and are often referred to as "servers" in the UI.

Source code in interactions/models/discord/guild.py
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class Guild(BaseGuild):
    """Guilds in Discord represent an isolated collection of users and channels, and are often referred to as "servers" in the UI."""

    unavailable: bool = attrs.field(repr=False, default=False)
    """True if this guild is unavailable due to an outage."""
    # owner: bool = attrs.field(repr=False, default=False)  # we get this from api but it's kinda useless to store
    afk_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None)
    """The channel id for afk."""
    afk_timeout: Optional[int] = attrs.field(repr=False, default=None)
    """afk timeout in seconds."""
    widget_enabled: bool = attrs.field(repr=False, default=False)
    """True if the server widget is enabled."""
    widget_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None)
    """The channel id that the widget will generate an invite to, or None if set to no invite."""
    verification_level: Union[VerificationLevel, int] = attrs.field(repr=False, default=VerificationLevel.NONE)
    """The verification level required for the guild."""
    default_message_notifications: Union[DefaultNotificationLevel, int] = attrs.field(
        repr=False, default=DefaultNotificationLevel.ALL_MESSAGES
    )
    """The default message notifications level."""
    explicit_content_filter: Union[ExplicitContentFilterLevel, int] = attrs.field(
        repr=False, default=ExplicitContentFilterLevel.DISABLED
    )
    """The explicit content filter level."""
    mfa_level: Union[MFALevel, int] = attrs.field(repr=False, default=MFALevel.NONE)
    """The required MFA (Multi Factor Authentication) level for the guild."""
    system_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None)
    """The id of the channel where guild notices such as welcome messages and boost events are posted."""
    system_channel_flags: SystemChannelFlags = attrs.field(
        repr=False, default=SystemChannelFlags.NONE, converter=SystemChannelFlags
    )
    """The system channel flags."""
    rules_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None)
    """The id of the channel where Community guilds can display rules and/or guidelines."""
    joined_at: str = attrs.field(repr=False, default=None, converter=optional(timestamp_converter))
    """When this guild was joined at."""
    large: bool = attrs.field(repr=False, default=False)
    """True if this is considered a large guild."""
    member_count: int = attrs.field(repr=False, default=0)
    """The total number of members in this guild."""
    presences: List[dict] = attrs.field(repr=False, factory=list)
    """The presences of the members in the guild, will only include non-offline members if the size is greater than large threshold."""
    max_presences: Optional[int] = attrs.field(repr=False, default=None)
    """The maximum number of presences for the guild. (None is always returned, apart from the largest of guilds)"""
    max_members: Optional[int] = attrs.field(repr=False, default=None)
    """The maximum number of members for the guild."""
    vanity_url_code: Optional[str] = attrs.field(repr=False, default=None)
    """The vanity url code for the guild."""
    banner: Optional[str] = attrs.field(repr=False, default=None)
    """Hash for banner image."""
    premium_tier: Optional[str] = attrs.field(repr=False, default=None)
    """The premium tier level. (Server Boost level)"""
    premium_subscription_count: int = attrs.field(repr=False, default=0)
    """The number of boosts this guild currently has."""
    preferred_locale: str = attrs.field(
        repr=False,
    )
    """The preferred locale of a Community guild. Used in server discovery and notices from Discord. Defaults to \"en-US\""""
    public_updates_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None)
    """The id of the channel where admins and moderators of Community guilds receive notices from Discord."""
    max_video_channel_users: int = attrs.field(repr=False, default=0)
    """The maximum amount of users in a video channel."""
    welcome_screen: Optional["GuildWelcome"] = attrs.field(repr=False, default=None)
    """The welcome screen of a Community guild, shown to new members, returned in an Invite's guild object."""
    nsfw_level: Union[NSFWLevel, int] = attrs.field(repr=False, default=NSFWLevel.DEFAULT)
    """The guild NSFW level."""
    stage_instances: List[dict] = attrs.field(repr=False, factory=list)  # TODO stage instance objects
    """Stage instances in the guild."""
    chunked = attrs.field(repr=False, factory=asyncio.Event, metadata=no_export_meta)
    """An event that is fired when this guild has been chunked"""
    command_permissions: dict[Snowflake_Type, CommandPermissions] = attrs.field(
        repr=False, factory=dict, metadata=no_export_meta
    )
    """A cache of all command permissions for this guild"""
    premium_progress_bar_enabled: bool = attrs.field(repr=False, default=False)
    """True if the guild has the boost progress bar enabled"""
    safety_alerts_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None)
    """The id of the channel where admins and moderators of Community guilds receive safety alerts from Discord."""

    _owner_id: Snowflake_Type = attrs.field(repr=False, converter=to_snowflake)
    _channel_ids: Set[Snowflake_Type] = attrs.field(repr=False, factory=set)
    _thread_ids: Set[Snowflake_Type] = attrs.field(repr=False, factory=set)
    _member_ids: Set[Snowflake_Type] = attrs.field(repr=False, factory=set)
    _role_ids: Set[Snowflake_Type] = attrs.field(repr=False, factory=set)
    _chunk_cache: list = attrs.field(repr=False, factory=list)
    _channel_gui_positions: Dict[Snowflake_Type, int] = attrs.field(repr=False, factory=dict)

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data = super()._process_dict(data, client)
        guild_id = data["id"]

        channels_data = data.pop("channels", [])
        for c in channels_data:
            c["guild_id"] = guild_id
        data["channel_ids"] = {client.cache.place_channel_data(channel_data).id for channel_data in channels_data}

        threads_data = data.pop("threads", [])
        data["thread_ids"] = {client.cache.place_channel_data(thread_data).id for thread_data in threads_data}

        members_data = data.pop("members", [])
        data["member_ids"] = {client.cache.place_member_data(guild_id, member_data).id for member_data in members_data}

        roles_data = data.pop("roles", [])
        data["role_ids"] = set(client.cache.place_role_data(guild_id, roles_data).keys())

        if welcome_screen := data.get("welcome_screen"):
            data["welcome_screen"] = GuildWelcome.from_dict(welcome_screen, client)

        if voice_states := data.get("voice_states"):
            [
                asyncio.create_task(client.cache.place_voice_state_data(state | {"guild_id": guild_id}))
                for state in voice_states
            ]
        return data

    @classmethod
    async def create(
        cls,
        name: str,
        client: "Client",
        *,
        icon: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
        verification_level: Absent["VerificationLevel"] = MISSING,
        default_message_notifications: Absent["DefaultNotificationLevel"] = MISSING,
        explicit_content_filter: Absent["ExplicitContentFilterLevel"] = MISSING,
        roles: Absent[list[dict]] = MISSING,
        channels: Absent[list[dict]] = MISSING,
        afk_channel_id: Absent["Snowflake_Type"] = MISSING,
        afk_timeout: Absent[int] = MISSING,
        system_channel_id: Absent["Snowflake_Type"] = MISSING,
        system_channel_flags: Absent["SystemChannelFlags"] = MISSING,
    ) -> "Guild":
        """
        Create a guild.

        !!! note
            This method will only work for bots in less than 10 guilds.

        ??? note "Param notes"
            Roles:
                - When using the `roles` parameter, the first member of the array is used to change properties of the guild's `@everyone` role. If you are trying to bootstrap a guild with additional roles, keep this in mind.
                - When using the `roles` parameter, the required id field within each role object is an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to overwrite a role's permissions in a channel when also passing in channels with the channels array.

            Channels:
                - When using the `channels` parameter, the position field is ignored, and none of the default channels are created.
                - When using the `channels` parameter, the id field within each channel object may be set to an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to create `GUILD_CATEGORY` channels by setting the `parent_id` field on any children to the category's id field. Category channels must be listed before any children.

        Args:
            name: name of the guild (2-100 characters)
            client: The client
            icon: An icon for the guild
            verification_level: The guild's verification level
            default_message_notifications: The default message notification level
            explicit_content_filter: The guild's explicit content filter level
            roles: An array of partial role dictionaries
            channels: An array of partial channel dictionaries
            afk_channel_id: id for afk channel
            afk_timeout: afk timeout in seconds
            system_channel_id: the id of the channel where guild notices should go
            system_channel_flags: flags for the system channel

        Returns:
            The created guild object

        """
        data = await client.http.create_guild(
            name=name,
            icon=to_image_data(icon) if icon else MISSING,
            verification_level=verification_level,
            default_message_notifications=default_message_notifications,
            explicit_content_filter=explicit_content_filter,
            roles=roles,
            channels=channels,
            afk_channel_id=afk_channel_id,
            afk_timeout=afk_timeout,
            system_channel_id=system_channel_id,
            system_channel_flags=int(system_channel_flags) if system_channel_flags else MISSING,
        )
        return client.cache.place_guild_data(data)

    @property
    def channels(self) -> List["models.TYPE_GUILD_CHANNEL"]:
        """Returns a list of channels associated with this guild."""
        channels = [self._client.cache.get_channel(c_id) for c_id in self._channel_ids]
        return [c for c in channels if c]

    @property
    def threads(self) -> List["models.TYPE_THREAD_CHANNEL"]:
        """Returns a list of threads associated with this guild."""
        return [self._client.cache.get_channel(t_id) for t_id in self._thread_ids]

    @property
    def members(self) -> List["models.Member"]:
        """Returns a list of all members within this guild."""
        members = (self._client.cache.get_member(self.id, m_id) for m_id in self._member_ids)
        return [m for m in members if m]

    @property
    def premium_subscribers(self) -> List["models.Member"]:
        """Returns a list of all premium subscribers"""
        return [member for member in self.members if member.premium]

    @property
    def bots(self) -> List["models.Member"]:
        """Returns a list of all bots within this guild"""
        return [member for member in self.members if member.bot]

    @property
    def humans(self) -> List["models.Member"]:
        """Returns a list of all humans within this guild"""
        return [member for member in self.members if not member.bot]

    @property
    def roles(self) -> List["models.Role"]:
        """Returns a list of roles associated with this guild."""
        return sorted((r for r_id in self._role_ids if (r := self._client.cache.get_role(r_id))), reverse=True)

    @property
    def me(self) -> "models.Member":
        """Returns this bots member object within this guild."""
        return self._client.cache.get_member(self.id, self._client.user.id)

    @property
    def system_channel(self) -> Optional["models.GuildText"]:
        """Returns the channel this guild uses for system messages."""
        return self._client.cache.get_channel(self.system_channel_id)

    @property
    def rules_channel(self) -> Optional["models.GuildText"]:
        """Returns the channel declared as a rules channel."""
        return self._client.cache.get_channel(self.rules_channel_id)

    @property
    def public_updates_channel(self) -> Optional["models.GuildText"]:
        """Returns the channel where server staff receive notices from Discord."""
        return self._client.cache.get_channel(self.public_updates_channel_id)

    @property
    def safety_alerts_channel(self) -> Optional["models.GuildText"]:
        """Returns the channel where server staff receive safety alerts from Discord."""
        return self._client.cache.get_channel(self.safety_alerts_channel_id)

    @property
    def emoji_limit(self) -> int:
        """The maximum number of emoji this guild can have."""
        base = 200 if "MORE_EMOJI" in self.features else 50
        return max(base, PREMIUM_GUILD_LIMITS[self.premium_tier]["emoji"])

    @property
    def sticker_limit(self) -> int:
        """The maximum number of stickers this guild can have."""
        base = 60 if "MORE_STICKERS" in self.features else 0
        return max(base, PREMIUM_GUILD_LIMITS[self.premium_tier]["stickers"])

    @property
    def bitrate_limit(self) -> int:
        """The maximum bitrate for this guild."""
        base = 128000 if "VIP_REGIONS" in self.features else 96000
        return max(base, PREMIUM_GUILD_LIMITS[self.premium_tier]["bitrate"])

    @property
    def filesize_limit(self) -> int:
        """The maximum filesize that may be uploaded within this guild."""
        return PREMIUM_GUILD_LIMITS[self.premium_tier]["filesize"]

    @property
    def default_role(self) -> "models.Role":
        """The `@everyone` role in this guild."""
        return self._client.cache.get_role(self.id)  # type: ignore

    @property
    def premium_subscriber_role(self) -> Optional["models.Role"]:
        """The role given to boosters of this server, if set."""
        return next((role for role in self.roles if role.premium_subscriber), None)

    @property
    def my_role(self) -> Optional["models.Role"]:
        """The role associated with this client, if set."""
        m_r_id = self._client.user.id
        return next((role for role in self.roles if role._bot_id == m_r_id), None)

    @property
    def permissions(self) -> Permissions:
        """Alias for me.guild_permissions"""
        return self.me.guild_permissions

    @property
    def voice_state(self) -> Optional["models.VoiceState"]:
        """Get the bot's voice state for the guild."""
        return self._client.cache.get_bot_voice_state(self.id)

    @property
    def voice_states(self) -> List["models.VoiceState"]:
        """Get a list of the active voice states in this guild."""
        # this is *very* ick, but we cache by user_id, so we have to do it this way,
        # alternative would be maintaining a lookup table in this guild object, which is inherently unreliable
        # noinspection PyProtectedMember
        return [v_state for v_state in self._client.cache.voice_state_cache.values() if v_state._guild_id == self.id]

    @property
    def mention_onboarding_customize(self) -> str:
        """Return a mention string for the customise section of Onboarding"""
        return "<id:customize>"

    @property
    def mention_onboarding_browse(self) -> str:
        """Return a mention string for the browse section of Onboarding"""
        return "<id:browse>"

    @property
    def mention_onboarding_guide(self) -> str:
        """Return a mention string for the guide section of Onboarding"""
        return "<id:guide>"

    async def fetch_member(self, member_id: Snowflake_Type, *, force: bool = False) -> Optional["models.Member"]:
        """
        Return the Member with the given discord ID, fetching from the API if necessary.

        Args:
            member_id: The ID of the member.
            force: Whether to force a fetch from the API.

        Returns:
            The member object fetched. If the member is not in this guild, returns None.

        """
        try:
            return await self._client.cache.fetch_member(self.id, member_id, force=force)
        except NotFound:
            return None

    def get_member(self, member_id: Snowflake_Type) -> Optional["models.Member"]:
        """
        Return the Member with the given discord ID.

        Args:
            member_id: The ID of the member

        Returns:
            Member object or None

        """
        return self._client.cache.get_member(self.id, member_id)

    async def fetch_owner(self, *, force: bool = False) -> "models.Member":
        """
        Return the Guild owner, fetching from the API if necessary.

        Args:
            force: Whether to force a fetch from the API.

        Returns:
            Member object or None

        """
        return await self._client.cache.fetch_member(self.id, self._owner_id, force=force)

    def get_owner(self) -> "models.Member":
        """
        Return the Guild owner

        Returns:
            Member object or None

        """
        return self._client.cache.get_member(self.id, self._owner_id)

    async def fetch_channels(self) -> List["models.TYPE_GUILD_CHANNEL"]:
        """
        Fetch this guild's channels.

        Returns:
            A list of channels in this guild

        """
        data = await self._client.http.get_guild_channels(self.id)
        return [self._client.cache.place_channel_data(channel_data) for channel_data in data]

    async def fetch_app_cmd_perms(self) -> dict[Snowflake_Type, "CommandPermissions"]:
        """
        Fetch the application command permissions for this guild.

        Returns:
            The application command permissions for this guild.

        """
        data = await self._client.http.batch_get_application_command_permissions(self._client.app.id, self.id)

        for command in data:
            command_permissions = CommandPermissions(client=self._client, command_id=command["id"], guild=self)
            perms = [ApplicationCommandPermission.from_dict(perm, self._client) for perm in command["permissions"]]

            command_permissions.update_permissions(*perms)

            self.command_permissions[int(command["id"])] = command_permissions

        return self.command_permissions

    def is_owner(self, user: Snowflake_Type) -> bool:
        """
        Whether the user is owner of the guild.

        Args:
            user: The user to check

        Returns:
            True if the user is the owner of the guild, False otherwise.

        !!! note
            the `user` argument can be any type that meets `Snowflake_Type`

        """
        return self._owner_id == to_snowflake(user)

    async def edit_nickname(self, new_nickname: Absent[str] = MISSING, reason: Absent[str] = MISSING) -> None:
        """
        Alias for me.edit_nickname

        Args:
            new_nickname: The new nickname to apply
            reason: The reason for this change

        !!! note
            Leave `new_nickname` empty to clean user's nickname

        """
        await self.me.edit_nickname(new_nickname, reason=reason)

    async def http_chunk(self) -> None:
        """Populates all members of this guild using the REST API."""
        start_time = time.perf_counter()

        iterator = MemberIterator(self)
        async for member in iterator:
            self._client.cache.place_member_data(self.id, member)

        self.chunked.set()
        self.logger.info(
            f"Cached {iterator.total_retrieved} members for {self.id} in {time.perf_counter() - start_time:.2f} seconds"
        )

    async def gateway_chunk(self, wait: bool = True, presences: bool = True) -> None:
        """
        Trigger a gateway `get_members` event, populating this object with members.

        Args:
            wait: Wait for chunking to be completed before continuing
            presences: Do you need presence data for members?

        """
        ws = self._client.get_guild_websocket(self.id)
        await ws.request_member_chunks(self.id, limit=0, presences=presences)
        if wait:
            await self.chunked.wait()

    async def chunk(self) -> None:
        """Populates all members of this guild using the REST API."""
        await self.http_chunk()

    async def chunk_guild(self, wait: bool = True, presences: bool = True) -> None:
        """
        Trigger a gateway `get_members` event, populating this object with members.

        !!! warning "Depreciation Warning"
            Gateway chunking is deprecated and replaced by http chunking. Use `guild.gateway_chunk` if you need gateway chunking.

        Args:
            wait: Wait for chunking to be completed before continuing
            presences: Do you need presence data for members?

        """
        warn(
            "Gateway chunking is deprecated and replaced by http chunking. Use `guild.gateway_chunk` if you need gateway chunking.",
            DeprecationWarning,
            stacklevel=2,
        )
        await self.gateway_chunk(wait=wait, presences=presences)

    async def process_member_chunk(self, chunk: dict) -> None:
        """
        Receive and either cache or process the chunks of members from gateway.

        Args:
            chunk: A member chunk from discord

        """
        if self.chunked.is_set():
            self.chunked.clear()

        if presences := chunk.get("presences"):
            # combine the presence dict into the members dict
            for presence in presences:
                u_id = presence["user"]["id"]
                # find the user this presence is for
                member_index = next(
                    (index for (index, d) in enumerate(chunk.get("members")) if d["user"]["id"] == u_id),
                    None,
                )
                del presence["user"]
                chunk["members"][member_index]["user"] = chunk["members"][member_index]["user"] | presence

        self._chunk_cache = self._chunk_cache + chunk.get("members") if self._chunk_cache else chunk.get("members")
        if chunk.get("chunk_index") != chunk.get("chunk_count") - 1:
            return self.logger.debug(f"Cached chunk of {len(chunk.get('members'))} members for {self.id}")
        members = self._chunk_cache
        self.logger.info(f"Processing {len(members)} members for {self.id}")

        s = time.monotonic()
        start_time = time.perf_counter()

        for member in members:
            self._client.cache.place_member_data(self.id, member)
            if (time.monotonic() - s) > 0.05:
                # look, i get this *could* be a thread, but because it needs to modify data in the main thread,
                # it is still blocking. So by periodically yielding to the event loop, we can avoid blocking, and still
                # process this data properly
                await asyncio.sleep(0)
                s = time.monotonic()

        total_time = time.perf_counter() - start_time
        self.chunk_cache = []
        self.logger.info(f"Cached members for {self.id} in {total_time:.2f} seconds")
        self.chunked.set()

    async def fetch_audit_log(
        self,
        user_id: Optional["Snowflake_Type"] = MISSING,
        action_type: Optional["AuditLogEventType"] = MISSING,
        before: Optional["Snowflake_Type"] = MISSING,
        after: Optional["Snowflake_Type"] = MISSING,
        limit: int = 100,
    ) -> "AuditLog":
        """
        Fetch section of the audit log for this guild.

        Args:
            user_id: The ID of the user to filter by
            action_type: The type of action to filter by
            before: The ID of the entry to start at
            after: The ID of the entry to end at
            limit: The number of entries to return

        Returns:
            An AuditLog object

        """
        data = await self._client.http.get_audit_log(self.id, user_id, action_type, before, after, limit)
        return AuditLog.from_dict(data, self._client)

    def audit_log_history(
        self,
        user_id: Optional["Snowflake_Type"] = MISSING,
        action_type: Optional["AuditLogEventType"] = MISSING,
        before: Optional["Snowflake_Type"] = MISSING,
        after: Optional["Snowflake_Type"] = MISSING,
        limit: int = 100,
    ) -> "AuditLogHistory":
        """
        Get an async iterator for the history of the audit log.

        Args:
            user_id (:class:`Snowflake_Type`): The user ID to search for.
            action_type (:class:`AuditLogEventType`): The action type to search for.
            before: get entries before this message ID
            after: get entries after this message ID
            limit: The maximum number of entries to return (set to 0 for no limit)

        ??? Hint "Example Usage:"
            ```python
            async for entry in guild.audit_log_history(limit=0):
                entry: "AuditLogEntry"
                if entry.changes:
                    # ...
            ```
            or
            ```python
            history = guild.audit_log_history(limit=250)
            # Flatten the async iterator into a list
            entries = await history.flatten()
            ```

        Returns:
            AuditLogHistory (AsyncIterator)

        """
        return AuditLogHistory(self, user_id, action_type, before, after, limit)

    async def edit(
        self,
        *,
        name: Absent[Optional[str]] = MISSING,
        description: Absent[Optional[str]] = MISSING,
        verification_level: Absent[Optional["VerificationLevel"]] = MISSING,
        default_message_notifications: Absent[Optional["DefaultNotificationLevel"]] = MISSING,
        explicit_content_filter: Absent[Optional["ExplicitContentFilterLevel"]] = MISSING,
        afk_channel: Absent[Optional[Union["models.GuildVoice", Snowflake_Type]]] = MISSING,
        afk_timeout: Absent[Optional[int]] = MISSING,
        system_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
        system_channel_flags: Absent[Union[SystemChannelFlags, int]] = MISSING,
        owner: Absent[Optional[Union["models.Member", Snowflake_Type]]] = MISSING,
        icon: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
        splash: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
        discovery_splash: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
        banner: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
        rules_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
        public_updates_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
        safety_alerts_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
        preferred_locale: Absent[Optional[str]] = MISSING,
        premium_progress_bar_enabled: Absent[Optional[bool]] = False,
        # ToDo: Fill in guild features. No idea how this works - https://discord.com/developers/docs/resources/guild#guild-object-guild-features
        features: Absent[Optional[list[str]]] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> None:
        """
        Edit the guild.

        Args:
            name: The new name of the guild.
            description: The new description of the guild.
            verification_level: The new verification level for the guild.
            default_message_notifications: The new notification level for the guild.
            explicit_content_filter: The new explicit content filter level for the guild.
            afk_channel: The voice channel that should be the new AFK channel.
            afk_timeout: How many seconds does a member need to be afk before they get moved to the AFK channel. Must be either `60`, `300`, `900`, `1800` or `3600`, otherwise HTTPException will be raised.
            icon: The new icon. Requires a bytes like object or a path to an image.
            owner: The new owner of the guild. You, the bot, need to be owner for this to work.
            splash: The new invite splash image. Requires a bytes like object or a path to an image.
            discovery_splash: The new discovery image. Requires a bytes like object or a path to an image.
            banner: The new banner image. Requires a bytes like object or a path to an image.
            system_channel: The text channel where new system messages should appear. This includes boosts and welcome messages.
            system_channel_flags: The new settings for the system channel.
            rules_channel: The text channel where your rules and community guidelines are displayed.
            public_updates_channel: The text channel where updates from discord should appear.
            safety_alerts_channel: The text channel where safety alerts from discord should appear.
            preferred_locale: The new preferred locale of the guild. Must be an ISO 639 code.
            premium_progress_bar_enabled: The status of the Nitro boost bar.
            features: The enabled guild features
            reason: An optional reason for the audit log.

        """
        await self._client.http.modify_guild(
            guild_id=self.id,
            name=name,
            description=description,
            verification_level=int(verification_level) if verification_level else MISSING,
            default_message_notifications=(
                int(default_message_notifications) if default_message_notifications else MISSING
            ),
            explicit_content_filter=int(explicit_content_filter) if explicit_content_filter else MISSING,
            afk_channel_id=to_snowflake(afk_channel) if afk_channel else MISSING,
            afk_timeout=afk_timeout,
            icon=to_image_data(icon) if icon else MISSING,
            owner_id=to_snowflake(owner) if owner else MISSING,
            splash=to_image_data(splash) if splash else MISSING,
            discovery_splash=to_image_data(discovery_splash) if discovery_splash else MISSING,
            banner=to_image_data(banner) if banner else MISSING,
            system_channel_id=to_snowflake(system_channel) if system_channel else MISSING,
            system_channel_flags=int(system_channel_flags) if system_channel_flags else MISSING,
            rules_channel_id=to_snowflake(rules_channel) if rules_channel else MISSING,
            public_updates_channel_id=to_snowflake(public_updates_channel) if public_updates_channel else MISSING,
            safety_alerts_channel_id=to_snowflake(safety_alerts_channel) if safety_alerts_channel else MISSING,
            preferred_locale=preferred_locale,
            premium_progress_bar_enabled=premium_progress_bar_enabled if premium_progress_bar_enabled else False,
            features=features,
            reason=reason,
        )

    async def create_custom_emoji(
        self,
        name: str,
        imagefile: UPLOADABLE_TYPE,
        roles: Absent[List[Union[Snowflake_Type, "models.Role"]]] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.CustomEmoji":
        """
        Create a new custom emoji for the guild.

        Args:
            name: Name of the emoji
            imagefile: The emoji image. (Supports PNG, JPEG, WebP, GIF)
            roles: Roles allowed to use this emoji.
            reason: An optional reason for the audit log.

        Returns:
            The new custom emoji created.

        """
        data_payload = {
            "name": name,
            "image": to_image_data(imagefile),
            "roles": to_snowflake_list(roles) if roles else MISSING,
        }

        emoji_data = await self._client.http.create_guild_emoji(data_payload, self.id, reason=reason)
        return self._client.cache.place_emoji_data(self.id, emoji_data)

    async def create_guild_template(self, name: str, description: Absent[str] = MISSING) -> "models.GuildTemplate":
        """
        Create a new guild template based on this guild.

        Args:
            name: The name of the template (1-100 characters)
            description: The description for the template (0-120 characters)

        Returns:
            The new guild template created.

        """
        template = await self._client.http.create_guild_template(self.id, name, description)
        return GuildTemplate.from_dict(template, self._client)

    async def fetch_guild_templates(self) -> List["models.GuildTemplate"]:
        """
        Fetch all guild templates for this guild.

        Returns:
            A list of guild template objects.

        """
        templates = await self._client.http.get_guild_templates(self.id)
        return GuildTemplate.from_list(templates, self._client)

    async def fetch_all_custom_emojis(self) -> List["models.CustomEmoji"]:
        """
        Gets all the custom emoji present for this guild.

        Returns:
            A list of custom emoji objects.

        """
        emojis_data = await self._client.http.get_all_guild_emoji(self.id)
        return [self._client.cache.place_emoji_data(self.id, emoji_data) for emoji_data in emojis_data]

    async def fetch_custom_emoji(
        self, emoji_id: Snowflake_Type, *, force: bool = False
    ) -> Optional["models.CustomEmoji"]:
        """
        Fetches the custom emoji present for this guild, based on the emoji id.

        Args:
            emoji_id: The target emoji to get data of.
            force: Whether to force fetch the emoji from the API.

        Returns:
            The custom emoji object. If the emoji is not found, returns None.

        """
        try:
            return await self._client.cache.fetch_emoji(self.id, emoji_id, force=force)
        except NotFound:
            return None

    def get_custom_emoji(self, emoji_id: Snowflake_Type) -> Optional["models.CustomEmoji"]:
        """
        Gets the custom emoji present for this guild, based on the emoji id.

        Args:
            emoji_id: The target emoji to get data of.

        Returns:
            The custom emoji object.

        """
        emoji_id = to_snowflake(emoji_id)
        emoji = self._client.cache.get_emoji(emoji_id)
        return emoji if emoji and emoji._guild_id == self.id else None

    async def create_channel(
        self,
        channel_type: Union[ChannelType, int],
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        category: Union[Snowflake_Type, "models.GuildCategory"] = None,
        nsfw: bool = False,
        bitrate: int = 64000,
        user_limit: int = 0,
        rate_limit_per_user: int = 0,
        reason: Absent[Optional[str]] = MISSING,
        **kwargs: dict,
    ) -> "models.TYPE_GUILD_CHANNEL":
        """
        Create a guild channel, allows for explicit channel type setting.

        Args:
            channel_type: The type of channel to create
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            category: The category this channel should be within
            nsfw: Should this channel be marked nsfw
            bitrate: The bitrate of this channel, only for voice
            user_limit: The max users that can be in this channel, only for voice
            rate_limit_per_user: The time users must wait between sending messages
            reason: The reason for creating this channel
            kwargs: Additional keyword arguments to pass to the channel creation

        Returns:
            The newly created channel.

        """
        channel_data = await self._client.http.create_guild_channel(
            self.id,
            name,
            channel_type,
            topic,
            position,
            models.process_permission_overwrites(permission_overwrites),
            to_optional_snowflake(category),
            nsfw,
            bitrate,
            user_limit,
            rate_limit_per_user,
            reason,
            **kwargs,
        )
        return self._client.cache.place_channel_data(channel_data)

    async def create_text_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        category: Union[Snowflake_Type, "models.GuildCategory"] = None,
        nsfw: bool = False,
        rate_limit_per_user: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.GuildText":
        """
        Create a text channel in this guild.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            category: The category this channel should be within
            nsfw: Should this channel be marked nsfw
            rate_limit_per_user: The time users must wait between sending messages
            reason: The reason for creating this channel

        Returns:
           The newly created text channel.

        """
        return await self.create_channel(
            channel_type=ChannelType.GUILD_TEXT,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            category=category,
            nsfw=nsfw,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
        )

    async def create_forum_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        category: Union[Snowflake_Type, "models.GuildCategory"] = None,
        nsfw: bool = False,
        rate_limit_per_user: int = 0,
        default_reaction_emoji: Absent[Union[dict, "models.PartialEmoji", "models.DefaultReaction", str]] = MISSING,
        available_tags: Absent["list[dict | models.ThreadTag] | dict | models.ThreadTag"] = MISSING,
        layout: ForumLayoutType = ForumLayoutType.NOT_SET,
        sort_order: Absent[ForumSortOrder] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.GuildForum":
        """
        Create a forum channel in this guild.

        Args:
            name: The name of the forum channel
            topic: The topic of the forum channel
            position: The position of the forum channel in the channel list
            permission_overwrites: Permission overwrites to apply to the forum channel
            category: The category this forum channel should be within
            nsfw: Should this forum be marked nsfw
            rate_limit_per_user: The time users must wait between sending messages
            default_reaction_emoji: The default emoji to react with when creating a thread
            available_tags: The available tags for this forum channel
            layout: The layout of the forum channel
            sort_order: The sort order of the forum channel
            reason: The reason for creating this channel

        Returns:
           The newly created forum channel.

        """
        return await self.create_channel(
            channel_type=ChannelType.GUILD_FORUM,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            category=category,
            nsfw=nsfw,
            rate_limit_per_user=rate_limit_per_user,
            default_reaction_emoji=models.process_default_reaction(default_reaction_emoji),
            available_tags=list_converter(models.process_thread_tag)(available_tags) if available_tags else MISSING,
            default_forum_layout=layout,
            default_sort_order=sort_order,
            reason=reason,
        )

    async def create_news_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        category: Union[Snowflake_Type, "models.GuildCategory"] = None,
        nsfw: bool = False,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.GuildNews":
        """
        Create a news channel in this guild.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            category: The category this channel should be within
            nsfw: Should this channel be marked nsfw
            reason: The reason for creating this channel

        Returns:
           The newly created news channel.

        """
        return await self.create_channel(
            channel_type=ChannelType.GUILD_NEWS,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            category=category,
            nsfw=nsfw,
            reason=reason,
        )

    async def create_voice_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        category: Union[Snowflake_Type, "models.GuildCategory"] = None,
        nsfw: bool = False,
        bitrate: int = 64000,
        user_limit: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.GuildVoice":
        """
        Create a guild voice channel.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            category: The category this channel should be within
            nsfw: Should this channel be marked nsfw
            bitrate: The bitrate of this channel, only for voice
            user_limit: The max users that can be in this channel, only for voice
            reason: The reason for creating this channel

        Returns:
           The newly created voice channel.

        """
        return await self.create_channel(
            channel_type=ChannelType.GUILD_VOICE,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            category=category,
            nsfw=nsfw,
            bitrate=bitrate,
            user_limit=user_limit,
            reason=reason,
        )

    async def create_stage_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        category: Absent[Union[Snowflake_Type, "models.GuildCategory"]] = MISSING,
        bitrate: int = 64000,
        user_limit: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.GuildStageVoice":
        """
        Create a guild stage channel.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            category: The category this channel should be within
            bitrate: The bitrate of this channel, only for voice
            user_limit: The max users that can be in this channel, only for voice
            reason: The reason for creating this channel

        Returns:
            The newly created stage channel.

        """
        return await self.create_channel(
            channel_type=ChannelType.GUILD_STAGE_VOICE,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            category=category,
            bitrate=bitrate,
            user_limit=user_limit,
            reason=reason,
        )

    async def create_category(
        self,
        name: str,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.GuildCategory":
        """
        Create a category within this guild.

        Args:
            name: The name of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            reason: The reason for creating this channel

        Returns:
            The newly created category.

        """
        return await self.create_channel(
            channel_type=ChannelType.GUILD_CATEGORY,
            name=name,
            position=position,
            permission_overwrites=permission_overwrites,
            reason=reason,
        )

    async def delete_channel(
        self, channel: Union["models.TYPE_GUILD_CHANNEL", Snowflake_Type], reason: str | None = None
    ) -> None:
        """
        Delete the given channel, can handle either a snowflake or channel object.

        This is effectively just an alias for `channel.delete()`

        Args:
            channel: The channel to be deleted
            reason: The reason for this deletion

        """
        if isinstance(channel, (str, int)):
            channel = await self._client.fetch_channel(channel)

        if not channel:
            raise ValueError("Unable to find requested channel")

        if channel.id not in self._channel_ids:
            raise ValueError("This guild does not hold the requested channel")

        await channel.delete(reason)

    async def list_scheduled_events(self, with_user_count: bool = False) -> List["models.ScheduledEvent"]:
        """
        List all scheduled events in this guild.

        Returns:
            A list of scheduled events.

        """
        scheduled_events_data = await self._client.http.list_schedules_events(self.id, with_user_count)
        return [self._client.cache.place_scheduled_event_data(data) for data in scheduled_events_data]

    def get_scheduled_event(self, scheduled_event_id: Snowflake_Type) -> Optional["models.ScheduledEvent"]:
        """
        Gets a scheduled event from the cache by id.

        Args:
            scheduled_event_id: The id of the scheduled event.

        Returns:
            The scheduled event. If the event does not exist, returns None.

        """
        event = self._client.cache.get_scheduled_event(scheduled_event_id)
        return None if event and int(event._guild_id) != self.id else event

    async def fetch_scheduled_event(
        self,
        scheduled_event_id: Snowflake_Type,
        with_user_count: bool = False,
        *,
        force: bool = False,
    ) -> Optional["models.ScheduledEvent"]:
        """
        Fetches a scheduled event by id.

        Args:
            scheduled_event_id: The id of the scheduled event.
            with_user_count: Whether to include the user count in the response.
            force: If the cache should be ignored, and the event should be fetched from the API

        Returns:
            The scheduled event. If the event does not exist, returns None.

        """
        try:
            return await self._client.cache.fetch_scheduled_event(
                self.id, scheduled_event_id, with_user_count=with_user_count, force=force
            )
        except NotFound:
            return None

    async def create_scheduled_event(
        self,
        name: str,
        event_type: ScheduledEventType,
        start_time: "models.Timestamp",
        end_time: Absent[Optional["models.Timestamp"]] = MISSING,
        description: Absent[Optional[str]] = MISSING,
        channel_id: Absent[Optional[Snowflake_Type]] = MISSING,
        external_location: Absent[Optional[str]] = MISSING,
        entity_metadata: Optional[dict] = None,
        privacy_level: ScheduledEventPrivacyLevel = ScheduledEventPrivacyLevel.GUILD_ONLY,
        cover_image: Absent[UPLOADABLE_TYPE] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.ScheduledEvent":
        """
        Create a scheduled guild event.

        Args:
            name: event name
            event_type: event type
            start_time: `Timestamp` object
            end_time: `Timestamp` object
            description: event description
            channel_id: channel id
            external_location: event external location (For external events)
            entity_metadata: event metadata (additional data for the event)
            privacy_level: event privacy level
            cover_image: the cover image of the scheduled event
            reason: reason for creating this scheduled event

        Returns:
            The newly created ScheduledEvent object

        !!! note
            For external events, external_location is required
            For voice/stage events, channel_id is required

        ??? note
            entity_metadata is the backend dictionary for fluff fields. Where possible, we plan to expose these fields directly.
            The full list of supported fields is https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-metadata
            Example: `entity_metadata=dict(location="cool place")`

        """
        if external_location is not MISSING:
            entity_metadata = {"location": external_location}

        if event_type == ScheduledEventType.EXTERNAL and external_location == MISSING:
            raise EventLocationNotProvided("Location is required for external events")

        payload = {
            "name": name,
            "entity_type": event_type,
            "scheduled_start_time": start_time.isoformat(),
            "scheduled_end_time": end_time.isoformat() if end_time is not MISSING else end_time,
            "description": description,
            "channel_id": channel_id,
            "entity_metadata": entity_metadata,
            "privacy_level": privacy_level,
            "image": to_image_data(cover_image) if cover_image else MISSING,
        }

        scheduled_event_data = await self._client.http.create_scheduled_event(self.id, payload, reason)
        return self._client.cache.place_scheduled_event_data(scheduled_event_data)

    async def create_custom_sticker(
        self,
        name: str,
        file: UPLOADABLE_TYPE,
        tags: list[str],
        description: Absent[Optional[str]] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.Sticker":
        """
        Creates a custom sticker for a guild.

        Args:
            name: The name of the sticker (2-30 characters)
            file: The sticker file to upload, must be a PNG, APNG, or Lottie JSON file (max 500 KB)
            tags: Autocomplete/suggestion tags for the sticker (max 200 characters)
            description: The description of the sticker (empty or 2-100 characters)
            reason: Reason for creating the sticker

        Returns:
            New Sticker instance

        """
        payload = {"name": name, "tags": " ".join(tags)}

        if description:
            payload["description"] = description

        sticker_data = await self._client.http.create_guild_sticker(payload, self.id, file=file, reason=reason)
        return models.Sticker.from_dict(sticker_data, self._client)

    async def fetch_all_custom_stickers(self) -> List["models.Sticker"]:
        """
        Fetches all custom stickers for a guild.

        Returns:
            List of Sticker objects

        """
        stickers_data = await self._client.http.list_guild_stickers(self.id)
        return models.Sticker.from_list(stickers_data, self._client)

    async def fetch_custom_sticker(self, sticker_id: Snowflake_Type) -> Optional["models.Sticker"]:
        """
        Fetches a specific custom sticker for a guild.

        Args:
            sticker_id: ID of sticker to get

        Returns:
            The custom sticker object. If the sticker does not exist, returns None.

        """
        try:
            sticker_data = await self._client.http.get_guild_sticker(self.id, to_snowflake(sticker_id))
        except NotFound:
            return None
        return models.Sticker.from_dict(sticker_data, self._client)

    async def fetch_all_webhooks(self) -> List["models.Webhook"]:
        """
        Fetches all the webhooks for this guild.

        Returns:
            A list of webhook objects.

        """
        webhooks_data = await self._client.http.get_guild_webhooks(self.id)
        return models.Webhook.from_list(webhooks_data, self._client)

    async def fetch_active_threads(self) -> "models.ThreadList":
        """
        Fetches all active threads in the guild, including public and private threads. Threads are ordered by their id, in descending order.

        Returns:
            List of active threads and thread member object for each returned thread the bot user has joined.

        """
        threads_data = await self._client.http.list_active_threads(self.id)
        return models.ThreadList.from_dict(threads_data, self._client)

    async def fetch_role(self, role_id: Snowflake_Type, *, force: bool = False) -> Optional["models.Role"]:
        """
        Fetch the specified role by ID.

        Args:
            role_id: The ID of the role to get
            force: Whether to force a fetch from the API

        Returns:
            The role object. If the role does not exist, returns None.

        """
        try:
            return await self._client.cache.fetch_role(self.id, role_id, force=force)
        except NotFound:
            return None

    def get_role(self, role_id: Snowflake_Type) -> Optional["models.Role"]:
        """
        Get the specified role by ID.

        Args:
            role_id: The ID of the role to get

        Returns:
            A role object or None if the role is not found.

        """
        role_id = to_snowflake(role_id)
        if role_id in self._role_ids:
            return self._client.cache.get_role(role_id)
        return None

    async def create_role(
        self,
        name: Absent[Optional[str]] = MISSING,
        permissions: Absent[Optional[Permissions]] = MISSING,
        colour: Absent[Optional[Union["models.Color", int]]] = MISSING,
        color: Absent[Optional[Union["models.Color", int]]] = MISSING,
        hoist: Optional[bool] = False,
        mentionable: Optional[bool] = False,
        icon: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.Role":
        """
        Create a new role for the guild. You must have the `manage roles` permission.

        Args:
            name: The name the role should have. `Default: new role`
            permissions: The permissions the role should have. `Default: @everyone permissions`
            colour: The colour of the role. Can be either `Color` or an RGB integer. `Default: BrandColors.BLACK`
            color: Alias for `colour`
            icon: Can be either a bytes like object or a path to an image, or a unicode emoji which is supported by discord.
            hoist: Whether the role is shown separately in the members list. `Default: False`
            mentionable: Whether the role can be mentioned. `Default: False`
            reason: An optional reason for the audit log.

        Returns:
            A role object or None if the role is not found.

        """
        payload = {}

        if name:
            payload["name"] = name

        if permissions is not MISSING and permissions is not None:
            payload["permissions"] = str(int(permissions))

        if colour := colour or color:
            payload["color"] = colour if isinstance(colour, int) else colour.value

        if hoist:
            payload["hoist"] = True

        if mentionable:
            payload["mentionable"] = True

        if icon:
            # test if the icon is probably a unicode emoji (str and len() == 1) or a path / bytes obj
            if isinstance(icon, str) and len(icon) == 1:
                payload["unicode_emoji"] = icon

            else:
                payload["icon"] = to_image_data(icon)

        result = await self._client.http.create_guild_role(guild_id=self.id, payload=payload, reason=reason)
        return self._client.cache.place_role_data(guild_id=self.id, data=[result])[to_snowflake(result["id"])]

    def get_channel(self, channel_id: Snowflake_Type) -> Optional["models.TYPE_GUILD_CHANNEL"]:
        """
        Returns a channel with the given `channel_id`.

        Args:
            channel_id: The ID of the channel to get

        Returns:
            Channel object if found, otherwise None

        """
        channel_id = to_snowflake(channel_id)
        if channel_id in self._channel_ids:
            # theoretically, this could get any channel the client can see,
            # but to make it less confusing to new programmers,
            # i intentionally check that the guild contains the channel first
            return self._client.cache.get_channel(channel_id)
        return None

    async def fetch_channel(
        self, channel_id: Snowflake_Type, *, force: bool = False
    ) -> Optional["models.TYPE_GUILD_CHANNEL"]:
        """
        Returns a channel with the given `channel_id` from the API.

        Args:
            channel_id: The ID of the channel to get
            force: Whether to force a fetch from the API

        Returns:
            The channel object. If the channel does not exist, returns None.

        """
        channel_id = to_snowflake(channel_id)
        if channel_id in self._channel_ids or not self._client.gateway_started:
            # The latter check here is to see if the bot is running with the gateway.
            # If not, then we need to check the API since only the gateway
            # populates the channel IDs

            # theoretically, this could get any channel the client can see,
            # but to make it less confusing to new programmers,
            # i intentionally check that the guild contains the channel first
            try:
                channel = await self._client.fetch_channel(channel_id, force=force)
                if channel._guild_id == self.id:
                    return channel
            except (NotFound, AttributeError):
                return None

        return None

    def get_thread(self, thread_id: Snowflake_Type) -> Optional["models.TYPE_THREAD_CHANNEL"]:
        """
        Returns a Thread with the given `thread_id`.

        Args:
            thread_id: The ID of the thread to get

        Returns:
            Thread object if found, otherwise None

        """
        thread_id = to_snowflake(thread_id)
        if thread_id in self._thread_ids:
            return self._client.cache.get_channel(thread_id)
        return None

    async def fetch_thread(
        self, thread_id: Snowflake_Type, *, force: bool = False
    ) -> Optional["models.TYPE_THREAD_CHANNEL"]:
        """
        Returns a Thread with the given `thread_id` from the API.

        Args:
            thread_id: The ID of the thread to get
            force: Whether to force a fetch from the API

        Returns:
            Thread object if found, otherwise None

        """
        thread_id = to_snowflake(thread_id)
        if thread_id in self._thread_ids:
            try:
                return await self._client.fetch_channel(thread_id, force=force)
            except NotFound:
                return None
        return None

    async def prune_members(
        self,
        days: int = 7,
        roles: Optional[List[Snowflake_Type]] = None,
        compute_prune_count: bool = True,
        reason: Absent[str] = MISSING,
    ) -> Optional[int]:
        """
        Begin a guild prune. Removes members from the guild who who have not interacted for the last `days` days. By default, members with roles are excluded from pruning, to include them, pass their role (or role id) in `roles` Requires `kick members` permission.

        Args:
            days: number of days to prune (1-30)
            roles: list of roles to include in the prune
            compute_prune_count: Whether the number of members pruned should be calculated (disable this for large guilds)
            reason: The reason for this prune

        Returns:
            The total number of members pruned, if `compute_prune_count` is set to True, otherwise None

        """
        if roles:
            roles = [str(to_snowflake(r)) for r in roles]

        resp = await self._client.http.begin_guild_prune(
            self.id,
            days,
            include_roles=roles,
            compute_prune_count=compute_prune_count,
            reason=reason,
        )
        return resp["pruned"]

    async def estimate_prune_members(
        self, days: int = 7, roles: List[Union[Snowflake_Type, "models.Role"]] = MISSING
    ) -> int:
        """
        Calculate how many members would be pruned, should `guild.prune_members` be used. By default, members with roles are excluded from pruning, to include them, pass their role (or role id) in `roles`.

        Args:
            days: number of days to prune (1-30)
            roles: list of roles to include in the prune

        Returns:
            Total number of members that would be pruned

        """
        if roles is not MISSING:
            roles = [r.id if isinstance(r, models.Role) else r for r in roles]
        else:
            roles = []

        resp = await self._client.http.get_guild_prune_count(self.id, days=days, include_roles=roles)
        return resp["pruned"]

    async def leave(self) -> None:
        """Leave this guild."""
        await self._client.http.leave_guild(self.id)

    async def delete(self) -> None:
        """
        Delete the guild.

        !!! note
            You must own this guild to do this.

        """
        await self._client.http.delete_guild(self.id)

    async def kick(
        self,
        user: Union["models.User", "models.Member", Snowflake_Type],
        reason: Absent[str] = MISSING,
    ) -> None:
        """
        Kick a user from the guild.

        !!! note
            You must have the `kick members` permission

        Args:
            user: The user to kick
            reason: The reason for the kick

        """
        await self._client.http.remove_guild_member(self.id, to_snowflake(user), reason=reason)

    async def ban(
        self,
        user: Union["models.User", "models.Member", Snowflake_Type],
        delete_message_days: Absent[int] = MISSING,
        delete_message_seconds: int = 0,
        reason: Absent[str] = MISSING,
    ) -> None:
        """
        Ban a user from the guild.

        !!! note
            You must have the `ban members` permission

        Args:
            user: The user to ban
            delete_message_days: (deprecated) How many days worth of messages to remove
            delete_message_seconds: How many seconds worth of messages to remove
            reason: The reason for the ban

        """
        if delete_message_days is not MISSING:
            warn(
                "delete_message_days is deprecated and will be removed in a future update",
                DeprecationWarning,
                stacklevel=2,
            )
            delete_message_seconds = delete_message_days * 3600
        await self._client.http.create_guild_ban(self.id, to_snowflake(user), delete_message_seconds, reason=reason)

    async def bulk_ban(
        self,
        users: List[Union["models.User", "models.Member", Snowflake_Type]],
        delete_message_seconds: int = 0,
        reason: Optional[str] = None,
    ) -> BulkBanResponse:
        """
        Bans a list of users from the guild.

        !!! note
            You must have the `ban members` permission

        Args:
            user: The users to ban
            delete_message_seconds: How many seconds worth of messages to remove
            reason: The reason for the ban

        """
        result = await self.client.http.bulk_guild_ban(
            self.id, [to_snowflake(user) for user in users], delete_message_seconds, reason=reason
        )
        return BulkBanResponse.from_dict(result, self.client)

    async def fetch_ban(self, user: Union["models.User", "models.Member", Snowflake_Type]) -> Optional[GuildBan]:
        """
        Fetches the ban information for the specified user in the guild. You must have the `ban members` permission.

        Args:
            user: The user to look up.

        Returns:
            The ban information. If the user is not banned, returns None.

        """
        try:
            ban_info = await self._client.http.get_guild_ban(self.id, to_snowflake(user))
        except NotFound:
            return None
        return GuildBan(reason=ban_info["reason"], user=self._client.cache.place_user_data(ban_info["user"]))

    async def fetch_bans(
        self,
        before: Optional["Snowflake_Type"] = MISSING,
        after: Optional["Snowflake_Type"] = MISSING,
        limit: int = 1000,
    ) -> list[GuildBan]:
        """
        Fetches bans for the guild. You must have the `ban members` permission.

        Args:
            before: consider only users before given user id
            after: consider only users after given user id
            limit: number of users to return (up to maximum 1000)

        Returns:
            A list containing bans and information about them.

        """
        ban_infos = await self._client.http.get_guild_bans(self.id, before=before, after=after, limit=limit)
        return [
            GuildBan(reason=ban_info["reason"], user=self._client.cache.place_user_data(ban_info["user"]))
            for ban_info in ban_infos
        ]

    async def create_auto_moderation_rule(
        self,
        name: str,
        *,
        trigger: BaseTrigger,
        actions: list[BaseAction],
        exempt_roles: list["Snowflake_Type"] = MISSING,
        exempt_channels: list["Snowflake_Type"] = MISSING,
        enabled: bool = True,
        event_type: AutoModEvent = AutoModEvent.MESSAGE_SEND,
    ) -> AutoModRule:
        """
        Create an auto-moderation rule in this guild.

        Args:
            name: The name of the rule
            trigger: The trigger for this rule
            actions: A list of actions to take upon triggering
            exempt_roles: Roles that ignore this rule
            exempt_channels: Channels that ignore this role
            enabled: Is this rule enabled?
            event_type: The type of event that triggers this rule

        Returns:
            The created rule

        """
        rule = AutoModRule(
            name=name,
            enabled=enabled,
            actions=actions,
            event_type=event_type,
            trigger=trigger,
            exempt_channels=exempt_channels if exempt_roles is not MISSING else [],
            exempt_roles=exempt_roles if exempt_roles is not MISSING else [],
            client=self._client,
        )
        data = await self._client.http.create_auto_moderation_rule(self.id, rule.to_dict())
        return AutoModRule.from_dict(data, self._client)

    async def fetch_auto_moderation_rules(self) -> List[AutoModRule]:
        """
        Get this guild's auto moderation rules.

        Returns:
            A list of auto moderation rules

        """
        data = await self._client.http.get_auto_moderation_rules(self.id)
        return [AutoModRule.from_dict(d, self._client) for d in data]

    async def delete_auto_moderation_rule(self, rule: "Snowflake_Type", reason: Absent[str] = MISSING) -> None:
        """
        Delete a given auto moderation rule.

        Args:
            rule: The rule to delete
            reason: The reason for deleting this rule

        """
        await self._client.http.delete_auto_moderation_rule(self.id, to_snowflake(rule), reason=reason)

    async def modify_auto_moderation_rule(
        self,
        rule: "Snowflake_Type",
        *,
        name: Absent[str] = MISSING,
        trigger: Absent[BaseTrigger] = MISSING,
        trigger_type: Absent[AutoModTriggerType] = MISSING,
        trigger_metadata: Absent[dict] = MISSING,
        actions: Absent[list[BaseAction]] = MISSING,
        exempt_channels: Absent[list["Snowflake_Type"]] = MISSING,
        exempt_roles: Absent[list["Snowflake_Type"]] = MISSING,
        event_type: Absent[AutoModEvent] = MISSING,
        enabled: Absent[bool] = MISSING,
        reason: Absent[str] = MISSING,
    ) -> AutoModRule:
        """
        Modify an existing automod rule.

        Args:
            rule: The rule to modify
            name: The name of the rule
            trigger: A trigger for this rule
            trigger_type: The type trigger for this rule (ignored if trigger specified)
            trigger_metadata: Metadata for the trigger (ignored if trigger specified)
            actions: A list of actions to take upon triggering
            exempt_roles: Roles that ignore this rule
            exempt_channels: Channels that ignore this role
            enabled: Is this rule enabled?
            event_type: The type of event that triggers this rule
            reason: The reason for this change

        Returns:
            The updated rule

        """
        if trigger:
            _data = trigger.to_dict()
            trigger_type = _data["trigger_type"]
            trigger_metadata = _data.get("trigger_metadata", {})

        out = await self._client.http.modify_auto_moderation_rule(
            self.id,
            to_snowflake(rule),
            name=name,
            trigger_type=trigger_type,
            trigger_metadata=trigger_metadata,
            actions=actions,
            exempt_roles=to_snowflake_list(exempt_roles) if exempt_roles is not MISSING else MISSING,
            exempt_channels=to_snowflake_list(exempt_channels) if exempt_channels is not MISSING else MISSING,
            event_type=event_type,
            enabled=enabled,
            reason=reason,
        )
        return AutoModRule.from_dict(out, self._client)

    async def unban(
        self,
        user: Union["models.User", "models.Member", Snowflake_Type],
        reason: Absent[str] = MISSING,
    ) -> None:
        """
        Unban a user from the guild.

        !!! note
            You must have the `ban members` permission

        Args:
            user: The user to unban
            reason: The reason for the ban

        """
        await self._client.http.remove_guild_ban(self.id, to_snowflake(user), reason=reason)

    async def fetch_widget_image(self, style: str | None = None) -> str:
        """
        Fetch a guilds widget image.

        For a list of styles, look here: https://discord.com/developers/docs/resources/guild#get-guild-widget-image-widget-style-options

        Args:
            style: The style to use for the widget image

        Returns:
            The URL of the widget image.

        """
        return await self._client.http.get_guild_widget_image(self.id, style)

    async def fetch_widget_settings(self) -> "GuildWidgetSettings":
        """
        Fetches the guilds widget settings.

        Returns:
            The guilds widget settings object.

        """
        return await GuildWidgetSettings.from_dict(await self._client.http.get_guild_widget_settings(self.id))

    async def fetch_widget(self) -> "GuildWidget":
        """
        Fetches the guilds widget.

        Returns:
            The guilds widget object.

        """
        return GuildWidget.from_dict(await self._client.http.get_guild_widget(self.id), self._client)

    async def modify_widget(
        self,
        enabled: Absent[bool] = MISSING,
        channel: Absent[Union["models.TYPE_GUILD_CHANNEL", Snowflake_Type]] = MISSING,
        settings: Absent["GuildWidgetSettings"] = MISSING,
    ) -> "GuildWidget":
        """
        Modify the guild's widget.

        Args:
            enabled: Should the widget be enabled?
            channel: The channel to use in the widget
            settings: The settings to use for the widget

        Returns:
            The updated guilds widget object.

        """
        if isinstance(settings, GuildWidgetSettings):
            enabled = settings.enabled
            channel = settings.channel_id

        channel = to_optional_snowflake(channel)
        return GuildWidget.from_dict(
            await self._client.http.modify_guild_widget(self.id, enabled, channel), self._client
        )

    async def fetch_invites(self) -> List["models.Invite"]:
        """
        Fetches all invites for the guild.

        Returns:
            A list of invites for the guild.

        """
        invites_data = await self._client.http.get_guild_invites(self.id)
        return models.Invite.from_list(invites_data, self._client)

    async def fetch_guild_integrations(self) -> List["models.GuildIntegration"]:
        """
        Fetches all integrations for the guild.

        Returns:
            A list of integrations for the guild.

        """
        data = await self._client.http.get_guild_integrations(self.id)
        return [GuildIntegration.from_dict(d | {"guild_id": self.id}, self._client) for d in data]

    async def search_members(self, query: str, limit: int = 1) -> List["models.Member"]:
        """
        Search for members in the guild whose username or nickname starts with a provided string.

        Args:
            query: Query string to match username(s) and nickname(s) against.
            limit: Max number of members to return (1-1000)

        Returns:
            A list of members matching the query.

        """
        data = await self._client.http.search_guild_members(guild_id=self.id, query=query, limit=limit)
        return [self._client.cache.place_member_data(self.id, _d) for _d in data]

    async def fetch_voice_regions(self) -> List["models.VoiceRegion"]:
        """
        Fetches the voice regions for the guild.

        Unlike the `Client.fetch_voice_regions` method, this will returns VIP servers when the guild is VIP-enabled.

        Returns:
            A list of voice regions.

        """
        regions_data = await self._client.http.get_guild_voice_regions(self.id)
        return models.VoiceRegion.from_list(regions_data)

    async def fetch_onboarding(self) -> Onboarding:
        """
        Fetches the guild's onboarding settings.

        Returns:
            The guild's onboarding settings.

        """
        return Onboarding.from_dict(await self._client.http.get_guild_onboarding(self.id), self._client)

    @property
    def gui_sorted_channels(self) -> list["models.TYPE_GUILD_CHANNEL"]:
        """Return this guilds channels sorted by their gui positions"""
        # create a sorted list of objects by their gui position
        if not self._channel_gui_positions:
            self._calculate_gui_channel_positions()
        return [
            self._client.get_channel(k)
            for k, v in sorted(self._channel_gui_positions.items(), key=lambda item: item[1])
        ]

    def get_channel_gui_position(self, channel_id: "Snowflake_Type") -> int:
        """
        Get a given channels gui position.

        Args:
            channel_id: The ID of the channel to get the gui position for.

        Returns:
            The gui position of the channel.

        """
        if not self._channel_gui_positions:
            self._calculate_gui_channel_positions()
        return self._channel_gui_positions.get(to_snowflake(channel_id), 0)

    def _calculate_gui_channel_positions(self) -> list["models.TYPE_GUILD_CHANNEL"]:
        """
        Calculates the GUI position for all known channels within this guild.

        Note this is an expensive operation and should only be run when actually required.

        Returns:
            The list of channels in this guild, sorted by their GUI position.

        """
        # sorting is based on this https://github.com/discord/discord-api-docs/issues/4613#issuecomment-1059997612
        sort_map = {
            ChannelType.GUILD_NEWS_THREAD: 1,
            ChannelType.GUILD_PUBLIC_THREAD: 1,
            ChannelType.GUILD_PRIVATE_THREAD: 1,
            ChannelType.GUILD_TEXT: 2,
            ChannelType.GUILD_CATEGORY: 2,
            ChannelType.GUILD_NEWS: 2,
            ChannelType.GUILD_FORUM: 2,  # assumed value
            ChannelType.GUILD_VOICE: 3,
            ChannelType.GUILD_STAGE_VOICE: 3,
        }

        def channel_sort_func(a, b) -> int:
            a_sorting = sort_map.get(a.type, 0)
            b_sorting = sort_map.get(b.type, 0)

            if a_sorting != b_sorting:
                return a_sorting - b_sorting
            return a.position - b.position or a.id - b.id

        sorted_channels = sorted(self.channels, key=cmp_to_key(channel_sort_func))

        for channel in sorted_channels[::-1]:
            if channel.parent_id:
                # sort channels under their respective categories
                sorted_channels.remove(channel)
                parent_index = sorted_channels.index(channel.category)
                sorted_channels.insert(parent_index + 1, channel)
            elif channel.type != ChannelType.GUILD_CATEGORY:
                # move non-category channels to the top
                sorted_channels.remove(channel)
                sorted_channels.insert(0, channel)

        self._channel_gui_positions = {channel.id: i for i, channel in enumerate(sorted_channels)}

        return sorted_channels

afk_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

The channel id for afk.

afk_timeout: Optional[int] = attrs.field(repr=False, default=None) class-attribute

afk timeout in seconds.

banner: Optional[str] = attrs.field(repr=False, default=None) class-attribute

Hash for banner image.

bitrate_limit: int property

The maximum bitrate for this guild.

bots: List[models.Member] property

Returns a list of all bots within this guild

channels: List[models.TYPE_GUILD_CHANNEL] property

Returns a list of channels associated with this guild.

chunked = attrs.field(repr=False, factory=asyncio.Event, metadata=no_export_meta) class-attribute

An event that is fired when this guild has been chunked

command_permissions: dict[Snowflake_Type, CommandPermissions] = attrs.field(repr=False, factory=dict, metadata=no_export_meta) class-attribute

A cache of all command permissions for this guild

default_message_notifications: Union[DefaultNotificationLevel, int] = attrs.field(repr=False, default=DefaultNotificationLevel.ALL_MESSAGES) class-attribute

The default message notifications level.

default_role: models.Role property

The @everyone role in this guild.

emoji_limit: int property

The maximum number of emoji this guild can have.

explicit_content_filter: Union[ExplicitContentFilterLevel, int] = attrs.field(repr=False, default=ExplicitContentFilterLevel.DISABLED) class-attribute

The explicit content filter level.

filesize_limit: int property

The maximum filesize that may be uploaded within this guild.

gui_sorted_channels: list[models.TYPE_GUILD_CHANNEL] property

Return this guilds channels sorted by their gui positions

humans: List[models.Member] property

Returns a list of all humans within this guild

joined_at: str = attrs.field(repr=False, default=None, converter=optional(timestamp_converter)) class-attribute

When this guild was joined at.

large: bool = attrs.field(repr=False, default=False) class-attribute

True if this is considered a large guild.

max_members: Optional[int] = attrs.field(repr=False, default=None) class-attribute

The maximum number of members for the guild.

max_presences: Optional[int] = attrs.field(repr=False, default=None) class-attribute

The maximum number of presences for the guild. (None is always returned, apart from the largest of guilds)

max_video_channel_users: int = attrs.field(repr=False, default=0) class-attribute

The maximum amount of users in a video channel.

me: models.Member property

Returns this bots member object within this guild.

member_count: int = attrs.field(repr=False, default=0) class-attribute

The total number of members in this guild.

members: List[models.Member] property

Returns a list of all members within this guild.

mention_onboarding_browse: str property

Return a mention string for the browse section of Onboarding

mention_onboarding_customize: str property

Return a mention string for the customise section of Onboarding

mention_onboarding_guide: str property

Return a mention string for the guide section of Onboarding

mfa_level: Union[MFALevel, int] = attrs.field(repr=False, default=MFALevel.NONE) class-attribute

The required MFA (Multi Factor Authentication) level for the guild.

my_role: Optional[models.Role] property

The role associated with this client, if set.

nsfw_level: Union[NSFWLevel, int] = attrs.field(repr=False, default=NSFWLevel.DEFAULT) class-attribute

The guild NSFW level.

permissions: Permissions property

Alias for me.guild_permissions

preferred_locale: str = attrs.field(repr=False) class-attribute

The preferred locale of a Community guild. Used in server discovery and notices from Discord. Defaults to "en-US"

premium_progress_bar_enabled: bool = attrs.field(repr=False, default=False) class-attribute

True if the guild has the boost progress bar enabled

premium_subscriber_role: Optional[models.Role] property

The role given to boosters of this server, if set.

premium_subscribers: List[models.Member] property

Returns a list of all premium subscribers

premium_subscription_count: int = attrs.field(repr=False, default=0) class-attribute

The number of boosts this guild currently has.

premium_tier: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The premium tier level. (Server Boost level)

presences: List[dict] = attrs.field(repr=False, factory=list) class-attribute

The presences of the members in the guild, will only include non-offline members if the size is greater than large threshold.

public_updates_channel: Optional[models.GuildText] property

Returns the channel where server staff receive notices from Discord.

public_updates_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

The id of the channel where admins and moderators of Community guilds receive notices from Discord.

roles: List[models.Role] property

Returns a list of roles associated with this guild.

rules_channel: Optional[models.GuildText] property

Returns the channel declared as a rules channel.

rules_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

The id of the channel where Community guilds can display rules and/or guidelines.

safety_alerts_channel: Optional[models.GuildText] property

Returns the channel where server staff receive safety alerts from Discord.

safety_alerts_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

The id of the channel where admins and moderators of Community guilds receive safety alerts from Discord.

stage_instances: List[dict] = attrs.field(repr=False, factory=list) class-attribute

Stage instances in the guild.

sticker_limit: int property

The maximum number of stickers this guild can have.

system_channel: Optional[models.GuildText] property

Returns the channel this guild uses for system messages.

system_channel_flags: SystemChannelFlags = attrs.field(repr=False, default=SystemChannelFlags.NONE, converter=SystemChannelFlags) class-attribute

The system channel flags.

system_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

The id of the channel where guild notices such as welcome messages and boost events are posted.

threads: List[models.TYPE_THREAD_CHANNEL] property

Returns a list of threads associated with this guild.

unavailable: bool = attrs.field(repr=False, default=False) class-attribute

True if this guild is unavailable due to an outage.

vanity_url_code: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The vanity url code for the guild.

verification_level: Union[VerificationLevel, int] = attrs.field(repr=False, default=VerificationLevel.NONE) class-attribute

The verification level required for the guild.

voice_state: Optional[models.VoiceState] property

Get the bot's voice state for the guild.

voice_states: List[models.VoiceState] property

Get a list of the active voice states in this guild.

welcome_screen: Optional[GuildWelcome] = attrs.field(repr=False, default=None) class-attribute

The welcome screen of a Community guild, shown to new members, returned in an Invite's guild object.

widget_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

The channel id that the widget will generate an invite to, or None if set to no invite.

widget_enabled: bool = attrs.field(repr=False, default=False) class-attribute

True if the server widget is enabled.

audit_log_history(user_id=MISSING, action_type=MISSING, before=MISSING, after=MISSING, limit=100)

Get an async iterator for the history of the audit log.

Parameters:

Name Type Description Default
user_id
MISSING
action_type
MISSING
before Optional[Snowflake_Type]

get entries before this message ID

MISSING
after Optional[Snowflake_Type]

get entries after this message ID

MISSING
limit int

The maximum number of entries to return (set to 0 for no limit)

100
Example Usage:

1
2
3
4
async for entry in guild.audit_log_history(limit=0):
    entry: "AuditLogEntry"
    if entry.changes:
        # ...
or
1
2
3
history = guild.audit_log_history(limit=250)
# Flatten the async iterator into a list
entries = await history.flatten()

Returns:

Type Description
AuditLogHistory

AuditLogHistory (AsyncIterator)

Source code in interactions/models/discord/guild.py
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
def audit_log_history(
    self,
    user_id: Optional["Snowflake_Type"] = MISSING,
    action_type: Optional["AuditLogEventType"] = MISSING,
    before: Optional["Snowflake_Type"] = MISSING,
    after: Optional["Snowflake_Type"] = MISSING,
    limit: int = 100,
) -> "AuditLogHistory":
    """
    Get an async iterator for the history of the audit log.

    Args:
        user_id (:class:`Snowflake_Type`): The user ID to search for.
        action_type (:class:`AuditLogEventType`): The action type to search for.
        before: get entries before this message ID
        after: get entries after this message ID
        limit: The maximum number of entries to return (set to 0 for no limit)

    ??? Hint "Example Usage:"
        ```python
        async for entry in guild.audit_log_history(limit=0):
            entry: "AuditLogEntry"
            if entry.changes:
                # ...
        ```
        or
        ```python
        history = guild.audit_log_history(limit=250)
        # Flatten the async iterator into a list
        entries = await history.flatten()
        ```

    Returns:
        AuditLogHistory (AsyncIterator)

    """
    return AuditLogHistory(self, user_id, action_type, before, after, limit)

ban(user, delete_message_days=MISSING, delete_message_seconds=0, reason=MISSING) async

Ban a user from the guild.

Note

You must have the ban members permission

Parameters:

Name Type Description Default
user Union[User, Member, Snowflake_Type]

The user to ban

required
delete_message_days Absent[int]

(deprecated) How many days worth of messages to remove

MISSING
delete_message_seconds int

How many seconds worth of messages to remove

0
reason Absent[str]

The reason for the ban

MISSING
Source code in interactions/models/discord/guild.py
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
async def ban(
    self,
    user: Union["models.User", "models.Member", Snowflake_Type],
    delete_message_days: Absent[int] = MISSING,
    delete_message_seconds: int = 0,
    reason: Absent[str] = MISSING,
) -> None:
    """
    Ban a user from the guild.

    !!! note
        You must have the `ban members` permission

    Args:
        user: The user to ban
        delete_message_days: (deprecated) How many days worth of messages to remove
        delete_message_seconds: How many seconds worth of messages to remove
        reason: The reason for the ban

    """
    if delete_message_days is not MISSING:
        warn(
            "delete_message_days is deprecated and will be removed in a future update",
            DeprecationWarning,
            stacklevel=2,
        )
        delete_message_seconds = delete_message_days * 3600
    await self._client.http.create_guild_ban(self.id, to_snowflake(user), delete_message_seconds, reason=reason)

bulk_ban(users, delete_message_seconds=0, reason=None) async

Bans a list of users from the guild.

Note

You must have the ban members permission

Parameters:

Name Type Description Default
user

The users to ban

required
delete_message_seconds int

How many seconds worth of messages to remove

0
reason Optional[str]

The reason for the ban

None
Source code in interactions/models/discord/guild.py
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
async def bulk_ban(
    self,
    users: List[Union["models.User", "models.Member", Snowflake_Type]],
    delete_message_seconds: int = 0,
    reason: Optional[str] = None,
) -> BulkBanResponse:
    """
    Bans a list of users from the guild.

    !!! note
        You must have the `ban members` permission

    Args:
        user: The users to ban
        delete_message_seconds: How many seconds worth of messages to remove
        reason: The reason for the ban

    """
    result = await self.client.http.bulk_guild_ban(
        self.id, [to_snowflake(user) for user in users], delete_message_seconds, reason=reason
    )
    return BulkBanResponse.from_dict(result, self.client)

chunk() async

Populates all members of this guild using the REST API.

Source code in interactions/models/discord/guild.py
649
650
651
async def chunk(self) -> None:
    """Populates all members of this guild using the REST API."""
    await self.http_chunk()

chunk_guild(wait=True, presences=True) async

Trigger a gateway get_members event, populating this object with members.

Depreciation Warning

Gateway chunking is deprecated and replaced by http chunking. Use guild.gateway_chunk if you need gateway chunking.

Parameters:

Name Type Description Default
wait bool

Wait for chunking to be completed before continuing

True
presences bool

Do you need presence data for members?

True
Source code in interactions/models/discord/guild.py
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
async def chunk_guild(self, wait: bool = True, presences: bool = True) -> None:
    """
    Trigger a gateway `get_members` event, populating this object with members.

    !!! warning "Depreciation Warning"
        Gateway chunking is deprecated and replaced by http chunking. Use `guild.gateway_chunk` if you need gateway chunking.

    Args:
        wait: Wait for chunking to be completed before continuing
        presences: Do you need presence data for members?

    """
    warn(
        "Gateway chunking is deprecated and replaced by http chunking. Use `guild.gateway_chunk` if you need gateway chunking.",
        DeprecationWarning,
        stacklevel=2,
    )
    await self.gateway_chunk(wait=wait, presences=presences)

create(name, client, *, icon=MISSING, verification_level=MISSING, default_message_notifications=MISSING, explicit_content_filter=MISSING, roles=MISSING, channels=MISSING, afk_channel_id=MISSING, afk_timeout=MISSING, system_channel_id=MISSING, system_channel_flags=MISSING) classmethod async

Create a guild.

Note

This method will only work for bots in less than 10 guilds.

Param notes

Roles: - When using the roles parameter, the first member of the array is used to change properties of the guild's @everyone role. If you are trying to bootstrap a guild with additional roles, keep this in mind. - When using the roles parameter, the required id field within each role object is an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to overwrite a role's permissions in a channel when also passing in channels with the channels array.

Channels: - When using the channels parameter, the position field is ignored, and none of the default channels are created. - When using the channels parameter, the id field within each channel object may be set to an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to create GUILD_CATEGORY channels by setting the parent_id field on any children to the category's id field. Category channels must be listed before any children.

Parameters:

Name Type Description Default
name str

name of the guild (2-100 characters)

required
client Client

The client

required
icon Absent[Optional[UPLOADABLE_TYPE]]

An icon for the guild

MISSING
verification_level Absent[VerificationLevel]

The guild's verification level

MISSING
default_message_notifications Absent[DefaultNotificationLevel]

The default message notification level

MISSING
explicit_content_filter Absent[ExplicitContentFilterLevel]

The guild's explicit content filter level

MISSING
roles Absent[list[dict]]

An array of partial role dictionaries

MISSING
channels Absent[list[dict]]

An array of partial channel dictionaries

MISSING
afk_channel_id Absent[Snowflake_Type]

id for afk channel

MISSING
afk_timeout Absent[int]

afk timeout in seconds

MISSING
system_channel_id Absent[Snowflake_Type]

the id of the channel where guild notices should go

MISSING
system_channel_flags Absent[SystemChannelFlags]

flags for the system channel

MISSING

Returns:

Type Description
Guild

The created guild object

Source code in interactions/models/discord/guild.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
@classmethod
async def create(
    cls,
    name: str,
    client: "Client",
    *,
    icon: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
    verification_level: Absent["VerificationLevel"] = MISSING,
    default_message_notifications: Absent["DefaultNotificationLevel"] = MISSING,
    explicit_content_filter: Absent["ExplicitContentFilterLevel"] = MISSING,
    roles: Absent[list[dict]] = MISSING,
    channels: Absent[list[dict]] = MISSING,
    afk_channel_id: Absent["Snowflake_Type"] = MISSING,
    afk_timeout: Absent[int] = MISSING,
    system_channel_id: Absent["Snowflake_Type"] = MISSING,
    system_channel_flags: Absent["SystemChannelFlags"] = MISSING,
) -> "Guild":
    """
    Create a guild.

    !!! note
        This method will only work for bots in less than 10 guilds.

    ??? note "Param notes"
        Roles:
            - When using the `roles` parameter, the first member of the array is used to change properties of the guild's `@everyone` role. If you are trying to bootstrap a guild with additional roles, keep this in mind.
            - When using the `roles` parameter, the required id field within each role object is an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to overwrite a role's permissions in a channel when also passing in channels with the channels array.

        Channels:
            - When using the `channels` parameter, the position field is ignored, and none of the default channels are created.
            - When using the `channels` parameter, the id field within each channel object may be set to an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to create `GUILD_CATEGORY` channels by setting the `parent_id` field on any children to the category's id field. Category channels must be listed before any children.

    Args:
        name: name of the guild (2-100 characters)
        client: The client
        icon: An icon for the guild
        verification_level: The guild's verification level
        default_message_notifications: The default message notification level
        explicit_content_filter: The guild's explicit content filter level
        roles: An array of partial role dictionaries
        channels: An array of partial channel dictionaries
        afk_channel_id: id for afk channel
        afk_timeout: afk timeout in seconds
        system_channel_id: the id of the channel where guild notices should go
        system_channel_flags: flags for the system channel

    Returns:
        The created guild object

    """
    data = await client.http.create_guild(
        name=name,
        icon=to_image_data(icon) if icon else MISSING,
        verification_level=verification_level,
        default_message_notifications=default_message_notifications,
        explicit_content_filter=explicit_content_filter,
        roles=roles,
        channels=channels,
        afk_channel_id=afk_channel_id,
        afk_timeout=afk_timeout,
        system_channel_id=system_channel_id,
        system_channel_flags=int(system_channel_flags) if system_channel_flags else MISSING,
    )
    return client.cache.place_guild_data(data)

create_auto_moderation_rule(name, *, trigger, actions, exempt_roles=MISSING, exempt_channels=MISSING, enabled=True, event_type=AutoModEvent.MESSAGE_SEND) async

Create an auto-moderation rule in this guild.

Parameters:

Name Type Description Default
name str

The name of the rule

required
trigger BaseTrigger

The trigger for this rule

required
actions list[BaseAction]

A list of actions to take upon triggering

required
exempt_roles list[Snowflake_Type]

Roles that ignore this rule

MISSING
exempt_channels list[Snowflake_Type]

Channels that ignore this role

MISSING
enabled bool

Is this rule enabled?

True
event_type AutoModEvent

The type of event that triggers this rule

AutoModEvent.MESSAGE_SEND

Returns:

Type Description
AutoModRule

The created rule

Source code in interactions/models/discord/guild.py
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
async def create_auto_moderation_rule(
    self,
    name: str,
    *,
    trigger: BaseTrigger,
    actions: list[BaseAction],
    exempt_roles: list["Snowflake_Type"] = MISSING,
    exempt_channels: list["Snowflake_Type"] = MISSING,
    enabled: bool = True,
    event_type: AutoModEvent = AutoModEvent.MESSAGE_SEND,
) -> AutoModRule:
    """
    Create an auto-moderation rule in this guild.

    Args:
        name: The name of the rule
        trigger: The trigger for this rule
        actions: A list of actions to take upon triggering
        exempt_roles: Roles that ignore this rule
        exempt_channels: Channels that ignore this role
        enabled: Is this rule enabled?
        event_type: The type of event that triggers this rule

    Returns:
        The created rule

    """
    rule = AutoModRule(
        name=name,
        enabled=enabled,
        actions=actions,
        event_type=event_type,
        trigger=trigger,
        exempt_channels=exempt_channels if exempt_roles is not MISSING else [],
        exempt_roles=exempt_roles if exempt_roles is not MISSING else [],
        client=self._client,
    )
    data = await self._client.http.create_auto_moderation_rule(self.id, rule.to_dict())
    return AutoModRule.from_dict(data, self._client)

create_category(name, position=MISSING, permission_overwrites=MISSING, reason=MISSING) async

Create a category within this guild.

Parameters:

Name Type Description Default
name str

The name of the channel

required
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildCategory

The newly created category.

Source code in interactions/models/discord/guild.py
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
async def create_category(
    self,
    name: str,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.GuildCategory":
    """
    Create a category within this guild.

    Args:
        name: The name of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        reason: The reason for creating this channel

    Returns:
        The newly created category.

    """
    return await self.create_channel(
        channel_type=ChannelType.GUILD_CATEGORY,
        name=name,
        position=position,
        permission_overwrites=permission_overwrites,
        reason=reason,
    )

create_channel(channel_type, name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, category=None, nsfw=False, bitrate=64000, user_limit=0, rate_limit_per_user=0, reason=MISSING, **kwargs) async

Create a guild channel, allows for explicit channel type setting.

Parameters:

Name Type Description Default
channel_type Union[ChannelType, int]

The type of channel to create

required
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
category Union[Snowflake_Type, GuildCategory]

The category this channel should be within

None
nsfw bool

Should this channel be marked nsfw

False
bitrate int

The bitrate of this channel, only for voice

64000
user_limit int

The max users that can be in this channel, only for voice

0
rate_limit_per_user int

The time users must wait between sending messages

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING
kwargs dict

Additional keyword arguments to pass to the channel creation

{}

Returns:

Type Description
TYPE_GUILD_CHANNEL

The newly created channel.

Source code in interactions/models/discord/guild.py
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
async def create_channel(
    self,
    channel_type: Union[ChannelType, int],
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    category: Union[Snowflake_Type, "models.GuildCategory"] = None,
    nsfw: bool = False,
    bitrate: int = 64000,
    user_limit: int = 0,
    rate_limit_per_user: int = 0,
    reason: Absent[Optional[str]] = MISSING,
    **kwargs: dict,
) -> "models.TYPE_GUILD_CHANNEL":
    """
    Create a guild channel, allows for explicit channel type setting.

    Args:
        channel_type: The type of channel to create
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        category: The category this channel should be within
        nsfw: Should this channel be marked nsfw
        bitrate: The bitrate of this channel, only for voice
        user_limit: The max users that can be in this channel, only for voice
        rate_limit_per_user: The time users must wait between sending messages
        reason: The reason for creating this channel
        kwargs: Additional keyword arguments to pass to the channel creation

    Returns:
        The newly created channel.

    """
    channel_data = await self._client.http.create_guild_channel(
        self.id,
        name,
        channel_type,
        topic,
        position,
        models.process_permission_overwrites(permission_overwrites),
        to_optional_snowflake(category),
        nsfw,
        bitrate,
        user_limit,
        rate_limit_per_user,
        reason,
        **kwargs,
    )
    return self._client.cache.place_channel_data(channel_data)

create_custom_emoji(name, imagefile, roles=MISSING, reason=MISSING) async

Create a new custom emoji for the guild.

Parameters:

Name Type Description Default
name str

Name of the emoji

required
imagefile UPLOADABLE_TYPE

The emoji image. (Supports PNG, JPEG, WebP, GIF)

required
roles Absent[List[Union[Snowflake_Type, Role]]]

Roles allowed to use this emoji.

MISSING
reason Absent[Optional[str]]

An optional reason for the audit log.

MISSING

Returns:

Type Description
CustomEmoji

The new custom emoji created.

Source code in interactions/models/discord/guild.py
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
async def create_custom_emoji(
    self,
    name: str,
    imagefile: UPLOADABLE_TYPE,
    roles: Absent[List[Union[Snowflake_Type, "models.Role"]]] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.CustomEmoji":
    """
    Create a new custom emoji for the guild.

    Args:
        name: Name of the emoji
        imagefile: The emoji image. (Supports PNG, JPEG, WebP, GIF)
        roles: Roles allowed to use this emoji.
        reason: An optional reason for the audit log.

    Returns:
        The new custom emoji created.

    """
    data_payload = {
        "name": name,
        "image": to_image_data(imagefile),
        "roles": to_snowflake_list(roles) if roles else MISSING,
    }

    emoji_data = await self._client.http.create_guild_emoji(data_payload, self.id, reason=reason)
    return self._client.cache.place_emoji_data(self.id, emoji_data)

create_custom_sticker(name, file, tags, description=MISSING, reason=MISSING) async

Creates a custom sticker for a guild.

Parameters:

Name Type Description Default
name str

The name of the sticker (2-30 characters)

required
file UPLOADABLE_TYPE

The sticker file to upload, must be a PNG, APNG, or Lottie JSON file (max 500 KB)

required
tags list[str]

Autocomplete/suggestion tags for the sticker (max 200 characters)

required
description Absent[Optional[str]]

The description of the sticker (empty or 2-100 characters)

MISSING
reason Absent[Optional[str]]

Reason for creating the sticker

MISSING

Returns:

Type Description
Sticker

New Sticker instance

Source code in interactions/models/discord/guild.py
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
async def create_custom_sticker(
    self,
    name: str,
    file: UPLOADABLE_TYPE,
    tags: list[str],
    description: Absent[Optional[str]] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.Sticker":
    """
    Creates a custom sticker for a guild.

    Args:
        name: The name of the sticker (2-30 characters)
        file: The sticker file to upload, must be a PNG, APNG, or Lottie JSON file (max 500 KB)
        tags: Autocomplete/suggestion tags for the sticker (max 200 characters)
        description: The description of the sticker (empty or 2-100 characters)
        reason: Reason for creating the sticker

    Returns:
        New Sticker instance

    """
    payload = {"name": name, "tags": " ".join(tags)}

    if description:
        payload["description"] = description

    sticker_data = await self._client.http.create_guild_sticker(payload, self.id, file=file, reason=reason)
    return models.Sticker.from_dict(sticker_data, self._client)

create_forum_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, category=None, nsfw=False, rate_limit_per_user=0, default_reaction_emoji=MISSING, available_tags=MISSING, layout=ForumLayoutType.NOT_SET, sort_order=MISSING, reason=MISSING) async

Create a forum channel in this guild.

Parameters:

Name Type Description Default
name str

The name of the forum channel

required
topic Absent[Optional[str]]

The topic of the forum channel

MISSING
position Absent[Optional[int]]

The position of the forum channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the forum channel

MISSING
category Union[Snowflake_Type, GuildCategory]

The category this forum channel should be within

None
nsfw bool

Should this forum be marked nsfw

False
rate_limit_per_user int

The time users must wait between sending messages

0
default_reaction_emoji Absent[Union[dict, PartialEmoji, DefaultReaction, str]]

The default emoji to react with when creating a thread

MISSING
available_tags Absent[ThreadTag]

The available tags for this forum channel

MISSING
layout ForumLayoutType

The layout of the forum channel

ForumLayoutType.NOT_SET
sort_order Absent[ForumSortOrder]

The sort order of the forum channel

MISSING
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildForum

The newly created forum channel.

Source code in interactions/models/discord/guild.py
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
async def create_forum_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    category: Union[Snowflake_Type, "models.GuildCategory"] = None,
    nsfw: bool = False,
    rate_limit_per_user: int = 0,
    default_reaction_emoji: Absent[Union[dict, "models.PartialEmoji", "models.DefaultReaction", str]] = MISSING,
    available_tags: Absent["list[dict | models.ThreadTag] | dict | models.ThreadTag"] = MISSING,
    layout: ForumLayoutType = ForumLayoutType.NOT_SET,
    sort_order: Absent[ForumSortOrder] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.GuildForum":
    """
    Create a forum channel in this guild.

    Args:
        name: The name of the forum channel
        topic: The topic of the forum channel
        position: The position of the forum channel in the channel list
        permission_overwrites: Permission overwrites to apply to the forum channel
        category: The category this forum channel should be within
        nsfw: Should this forum be marked nsfw
        rate_limit_per_user: The time users must wait between sending messages
        default_reaction_emoji: The default emoji to react with when creating a thread
        available_tags: The available tags for this forum channel
        layout: The layout of the forum channel
        sort_order: The sort order of the forum channel
        reason: The reason for creating this channel

    Returns:
       The newly created forum channel.

    """
    return await self.create_channel(
        channel_type=ChannelType.GUILD_FORUM,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        category=category,
        nsfw=nsfw,
        rate_limit_per_user=rate_limit_per_user,
        default_reaction_emoji=models.process_default_reaction(default_reaction_emoji),
        available_tags=list_converter(models.process_thread_tag)(available_tags) if available_tags else MISSING,
        default_forum_layout=layout,
        default_sort_order=sort_order,
        reason=reason,
    )

create_guild_template(name, description=MISSING) async

Create a new guild template based on this guild.

Parameters:

Name Type Description Default
name str

The name of the template (1-100 characters)

required
description Absent[str]

The description for the template (0-120 characters)

MISSING

Returns:

Type Description
GuildTemplate

The new guild template created.

Source code in interactions/models/discord/guild.py
890
891
892
893
894
895
896
897
898
899
900
901
902
903
async def create_guild_template(self, name: str, description: Absent[str] = MISSING) -> "models.GuildTemplate":
    """
    Create a new guild template based on this guild.

    Args:
        name: The name of the template (1-100 characters)
        description: The description for the template (0-120 characters)

    Returns:
        The new guild template created.

    """
    template = await self._client.http.create_guild_template(self.id, name, description)
    return GuildTemplate.from_dict(template, self._client)

create_news_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, category=None, nsfw=False, reason=MISSING) async

Create a news channel in this guild.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
category Union[Snowflake_Type, GuildCategory]

The category this channel should be within

None
nsfw bool

Should this channel be marked nsfw

False
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildNews

The newly created news channel.

Source code in interactions/models/discord/guild.py
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
async def create_news_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    category: Union[Snowflake_Type, "models.GuildCategory"] = None,
    nsfw: bool = False,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.GuildNews":
    """
    Create a news channel in this guild.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        category: The category this channel should be within
        nsfw: Should this channel be marked nsfw
        reason: The reason for creating this channel

    Returns:
       The newly created news channel.

    """
    return await self.create_channel(
        channel_type=ChannelType.GUILD_NEWS,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        category=category,
        nsfw=nsfw,
        reason=reason,
    )

create_role(name=MISSING, permissions=MISSING, colour=MISSING, color=MISSING, hoist=False, mentionable=False, icon=MISSING, reason=MISSING) async

Create a new role for the guild. You must have the manage roles permission.

Parameters:

Name Type Description Default
name Absent[Optional[str]]

The name the role should have. Default: new role

MISSING
permissions Absent[Optional[Permissions]]

The permissions the role should have. Default: @everyone permissions

MISSING
colour Absent[Optional[Union[Color, int]]]

The colour of the role. Can be either Color or an RGB integer. Default: BrandColors.BLACK

MISSING
color Absent[Optional[Union[Color, int]]]

Alias for colour

MISSING
icon Absent[Optional[UPLOADABLE_TYPE]]

Can be either a bytes like object or a path to an image, or a unicode emoji which is supported by discord.

MISSING
hoist Optional[bool]

Whether the role is shown separately in the members list. Default: False

False
mentionable Optional[bool]

Whether the role can be mentioned. Default: False

False
reason Absent[Optional[str]]

An optional reason for the audit log.

MISSING

Returns:

Type Description
Role

A role object or None if the role is not found.

Source code in interactions/models/discord/guild.py
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
async def create_role(
    self,
    name: Absent[Optional[str]] = MISSING,
    permissions: Absent[Optional[Permissions]] = MISSING,
    colour: Absent[Optional[Union["models.Color", int]]] = MISSING,
    color: Absent[Optional[Union["models.Color", int]]] = MISSING,
    hoist: Optional[bool] = False,
    mentionable: Optional[bool] = False,
    icon: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.Role":
    """
    Create a new role for the guild. You must have the `manage roles` permission.

    Args:
        name: The name the role should have. `Default: new role`
        permissions: The permissions the role should have. `Default: @everyone permissions`
        colour: The colour of the role. Can be either `Color` or an RGB integer. `Default: BrandColors.BLACK`
        color: Alias for `colour`
        icon: Can be either a bytes like object or a path to an image, or a unicode emoji which is supported by discord.
        hoist: Whether the role is shown separately in the members list. `Default: False`
        mentionable: Whether the role can be mentioned. `Default: False`
        reason: An optional reason for the audit log.

    Returns:
        A role object or None if the role is not found.

    """
    payload = {}

    if name:
        payload["name"] = name

    if permissions is not MISSING and permissions is not None:
        payload["permissions"] = str(int(permissions))

    if colour := colour or color:
        payload["color"] = colour if isinstance(colour, int) else colour.value

    if hoist:
        payload["hoist"] = True

    if mentionable:
        payload["mentionable"] = True

    if icon:
        # test if the icon is probably a unicode emoji (str and len() == 1) or a path / bytes obj
        if isinstance(icon, str) and len(icon) == 1:
            payload["unicode_emoji"] = icon

        else:
            payload["icon"] = to_image_data(icon)

    result = await self._client.http.create_guild_role(guild_id=self.id, payload=payload, reason=reason)
    return self._client.cache.place_role_data(guild_id=self.id, data=[result])[to_snowflake(result["id"])]

create_scheduled_event(name, event_type, start_time, end_time=MISSING, description=MISSING, channel_id=MISSING, external_location=MISSING, entity_metadata=None, privacy_level=ScheduledEventPrivacyLevel.GUILD_ONLY, cover_image=MISSING, reason=MISSING) async

Create a scheduled guild event.

Parameters:

Name Type Description Default
name str

event name

required
event_type ScheduledEventType

event type

required
start_time Timestamp

Timestamp object

required
end_time Absent[Optional[Timestamp]]

Timestamp object

MISSING
description Absent[Optional[str]]

event description

MISSING
channel_id Absent[Optional[Snowflake_Type]]

channel id

MISSING
external_location Absent[Optional[str]]

event external location (For external events)

MISSING
entity_metadata Optional[dict]

event metadata (additional data for the event)

None
privacy_level ScheduledEventPrivacyLevel

event privacy level

ScheduledEventPrivacyLevel.GUILD_ONLY
cover_image Absent[UPLOADABLE_TYPE]

the cover image of the scheduled event

MISSING
reason Absent[Optional[str]]

reason for creating this scheduled event

MISSING

Returns:

Type Description
ScheduledEvent

The newly created ScheduledEvent object

Note

For external events, external_location is required For voice/stage events, channel_id is required

Note

entity_metadata is the backend dictionary for fluff fields. Where possible, we plan to expose these fields directly. The full list of supported fields is https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-metadata Example: entity_metadata=dict(location="cool place")

Source code in interactions/models/discord/guild.py
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
async def create_scheduled_event(
    self,
    name: str,
    event_type: ScheduledEventType,
    start_time: "models.Timestamp",
    end_time: Absent[Optional["models.Timestamp"]] = MISSING,
    description: Absent[Optional[str]] = MISSING,
    channel_id: Absent[Optional[Snowflake_Type]] = MISSING,
    external_location: Absent[Optional[str]] = MISSING,
    entity_metadata: Optional[dict] = None,
    privacy_level: ScheduledEventPrivacyLevel = ScheduledEventPrivacyLevel.GUILD_ONLY,
    cover_image: Absent[UPLOADABLE_TYPE] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.ScheduledEvent":
    """
    Create a scheduled guild event.

    Args:
        name: event name
        event_type: event type
        start_time: `Timestamp` object
        end_time: `Timestamp` object
        description: event description
        channel_id: channel id
        external_location: event external location (For external events)
        entity_metadata: event metadata (additional data for the event)
        privacy_level: event privacy level
        cover_image: the cover image of the scheduled event
        reason: reason for creating this scheduled event

    Returns:
        The newly created ScheduledEvent object

    !!! note
        For external events, external_location is required
        For voice/stage events, channel_id is required

    ??? note
        entity_metadata is the backend dictionary for fluff fields. Where possible, we plan to expose these fields directly.
        The full list of supported fields is https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-metadata
        Example: `entity_metadata=dict(location="cool place")`

    """
    if external_location is not MISSING:
        entity_metadata = {"location": external_location}

    if event_type == ScheduledEventType.EXTERNAL and external_location == MISSING:
        raise EventLocationNotProvided("Location is required for external events")

    payload = {
        "name": name,
        "entity_type": event_type,
        "scheduled_start_time": start_time.isoformat(),
        "scheduled_end_time": end_time.isoformat() if end_time is not MISSING else end_time,
        "description": description,
        "channel_id": channel_id,
        "entity_metadata": entity_metadata,
        "privacy_level": privacy_level,
        "image": to_image_data(cover_image) if cover_image else MISSING,
    }

    scheduled_event_data = await self._client.http.create_scheduled_event(self.id, payload, reason)
    return self._client.cache.place_scheduled_event_data(scheduled_event_data)

create_stage_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, category=MISSING, bitrate=64000, user_limit=0, reason=MISSING) async

Create a guild stage channel.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
category Absent[Union[Snowflake_Type, GuildCategory]]

The category this channel should be within

MISSING
bitrate int

The bitrate of this channel, only for voice

64000
user_limit int

The max users that can be in this channel, only for voice

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildStageVoice

The newly created stage channel.

Source code in interactions/models/discord/guild.py
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
async def create_stage_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    category: Absent[Union[Snowflake_Type, "models.GuildCategory"]] = MISSING,
    bitrate: int = 64000,
    user_limit: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.GuildStageVoice":
    """
    Create a guild stage channel.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        category: The category this channel should be within
        bitrate: The bitrate of this channel, only for voice
        user_limit: The max users that can be in this channel, only for voice
        reason: The reason for creating this channel

    Returns:
        The newly created stage channel.

    """
    return await self.create_channel(
        channel_type=ChannelType.GUILD_STAGE_VOICE,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        category=category,
        bitrate=bitrate,
        user_limit=user_limit,
        reason=reason,
    )

create_text_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, category=None, nsfw=False, rate_limit_per_user=0, reason=MISSING) async

Create a text channel in this guild.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
category Union[Snowflake_Type, GuildCategory]

The category this channel should be within

None
nsfw bool

Should this channel be marked nsfw

False
rate_limit_per_user int

The time users must wait between sending messages

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildText

The newly created text channel.

Source code in interactions/models/discord/guild.py
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
async def create_text_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    category: Union[Snowflake_Type, "models.GuildCategory"] = None,
    nsfw: bool = False,
    rate_limit_per_user: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.GuildText":
    """
    Create a text channel in this guild.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        category: The category this channel should be within
        nsfw: Should this channel be marked nsfw
        rate_limit_per_user: The time users must wait between sending messages
        reason: The reason for creating this channel

    Returns:
       The newly created text channel.

    """
    return await self.create_channel(
        channel_type=ChannelType.GUILD_TEXT,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        category=category,
        nsfw=nsfw,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
    )

create_voice_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, category=None, nsfw=False, bitrate=64000, user_limit=0, reason=MISSING) async

Create a guild voice channel.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
category Union[Snowflake_Type, GuildCategory]

The category this channel should be within

None
nsfw bool

Should this channel be marked nsfw

False
bitrate int

The bitrate of this channel, only for voice

64000
user_limit int

The max users that can be in this channel, only for voice

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildVoice

The newly created voice channel.

Source code in interactions/models/discord/guild.py
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
async def create_voice_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    category: Union[Snowflake_Type, "models.GuildCategory"] = None,
    nsfw: bool = False,
    bitrate: int = 64000,
    user_limit: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.GuildVoice":
    """
    Create a guild voice channel.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        category: The category this channel should be within
        nsfw: Should this channel be marked nsfw
        bitrate: The bitrate of this channel, only for voice
        user_limit: The max users that can be in this channel, only for voice
        reason: The reason for creating this channel

    Returns:
       The newly created voice channel.

    """
    return await self.create_channel(
        channel_type=ChannelType.GUILD_VOICE,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        category=category,
        nsfw=nsfw,
        bitrate=bitrate,
        user_limit=user_limit,
        reason=reason,
    )

delete() async

Delete the guild.

Note

You must own this guild to do this.

Source code in interactions/models/discord/guild.py
1723
1724
1725
1726
1727
1728
1729
1730
1731
async def delete(self) -> None:
    """
    Delete the guild.

    !!! note
        You must own this guild to do this.

    """
    await self._client.http.delete_guild(self.id)

delete_auto_moderation_rule(rule, reason=MISSING) async

Delete a given auto moderation rule.

Parameters:

Name Type Description Default
rule Snowflake_Type

The rule to delete

required
reason Absent[str]

The reason for deleting this rule

MISSING
Source code in interactions/models/discord/guild.py
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
async def delete_auto_moderation_rule(self, rule: "Snowflake_Type", reason: Absent[str] = MISSING) -> None:
    """
    Delete a given auto moderation rule.

    Args:
        rule: The rule to delete
        reason: The reason for deleting this rule

    """
    await self._client.http.delete_auto_moderation_rule(self.id, to_snowflake(rule), reason=reason)

delete_channel(channel, reason=None) async

Delete the given channel, can handle either a snowflake or channel object.

This is effectively just an alias for channel.delete()

Parameters:

Name Type Description Default
channel Union[TYPE_GUILD_CHANNEL, Snowflake_Type]

The channel to be deleted

required
reason str | None

The reason for this deletion

None
Source code in interactions/models/discord/guild.py
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
async def delete_channel(
    self, channel: Union["models.TYPE_GUILD_CHANNEL", Snowflake_Type], reason: str | None = None
) -> None:
    """
    Delete the given channel, can handle either a snowflake or channel object.

    This is effectively just an alias for `channel.delete()`

    Args:
        channel: The channel to be deleted
        reason: The reason for this deletion

    """
    if isinstance(channel, (str, int)):
        channel = await self._client.fetch_channel(channel)

    if not channel:
        raise ValueError("Unable to find requested channel")

    if channel.id not in self._channel_ids:
        raise ValueError("This guild does not hold the requested channel")

    await channel.delete(reason)

edit(*, name=MISSING, description=MISSING, verification_level=MISSING, default_message_notifications=MISSING, explicit_content_filter=MISSING, afk_channel=MISSING, afk_timeout=MISSING, system_channel=MISSING, system_channel_flags=MISSING, owner=MISSING, icon=MISSING, splash=MISSING, discovery_splash=MISSING, banner=MISSING, rules_channel=MISSING, public_updates_channel=MISSING, safety_alerts_channel=MISSING, preferred_locale=MISSING, premium_progress_bar_enabled=False, features=MISSING, reason=MISSING) async

Edit the guild.

Parameters:

Name Type Description Default
name Absent[Optional[str]]

The new name of the guild.

MISSING
description Absent[Optional[str]]

The new description of the guild.

MISSING
verification_level Absent[Optional[VerificationLevel]]

The new verification level for the guild.

MISSING
default_message_notifications Absent[Optional[DefaultNotificationLevel]]

The new notification level for the guild.

MISSING
explicit_content_filter Absent[Optional[ExplicitContentFilterLevel]]

The new explicit content filter level for the guild.

MISSING
afk_channel Absent[Optional[Union[GuildVoice, Snowflake_Type]]]

The voice channel that should be the new AFK channel.

MISSING
afk_timeout Absent[Optional[int]]

How many seconds does a member need to be afk before they get moved to the AFK channel. Must be either 60, 300, 900, 1800 or 3600, otherwise HTTPException will be raised.

MISSING
icon Absent[Optional[UPLOADABLE_TYPE]]

The new icon. Requires a bytes like object or a path to an image.

MISSING
owner Absent[Optional[Union[Member, Snowflake_Type]]]

The new owner of the guild. You, the bot, need to be owner for this to work.

MISSING
splash Absent[Optional[UPLOADABLE_TYPE]]

The new invite splash image. Requires a bytes like object or a path to an image.

MISSING
discovery_splash Absent[Optional[UPLOADABLE_TYPE]]

The new discovery image. Requires a bytes like object or a path to an image.

MISSING
banner Absent[Optional[UPLOADABLE_TYPE]]

The new banner image. Requires a bytes like object or a path to an image.

MISSING
system_channel Absent[Optional[Union[GuildText, Snowflake_Type]]]

The text channel where new system messages should appear. This includes boosts and welcome messages.

MISSING
system_channel_flags Absent[Union[SystemChannelFlags, int]]

The new settings for the system channel.

MISSING
rules_channel Absent[Optional[Union[GuildText, Snowflake_Type]]]

The text channel where your rules and community guidelines are displayed.

MISSING
public_updates_channel Absent[Optional[Union[GuildText, Snowflake_Type]]]

The text channel where updates from discord should appear.

MISSING
safety_alerts_channel Absent[Optional[Union[GuildText, Snowflake_Type]]]

The text channel where safety alerts from discord should appear.

MISSING
preferred_locale Absent[Optional[str]]

The new preferred locale of the guild. Must be an ISO 639 code.

MISSING
premium_progress_bar_enabled Absent[Optional[bool]]

The status of the Nitro boost bar.

False
features Absent[Optional[list[str]]]

The enabled guild features

MISSING
reason Absent[Optional[str]]

An optional reason for the audit log.

MISSING
Source code in interactions/models/discord/guild.py
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
async def edit(
    self,
    *,
    name: Absent[Optional[str]] = MISSING,
    description: Absent[Optional[str]] = MISSING,
    verification_level: Absent[Optional["VerificationLevel"]] = MISSING,
    default_message_notifications: Absent[Optional["DefaultNotificationLevel"]] = MISSING,
    explicit_content_filter: Absent[Optional["ExplicitContentFilterLevel"]] = MISSING,
    afk_channel: Absent[Optional[Union["models.GuildVoice", Snowflake_Type]]] = MISSING,
    afk_timeout: Absent[Optional[int]] = MISSING,
    system_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
    system_channel_flags: Absent[Union[SystemChannelFlags, int]] = MISSING,
    owner: Absent[Optional[Union["models.Member", Snowflake_Type]]] = MISSING,
    icon: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
    splash: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
    discovery_splash: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
    banner: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
    rules_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
    public_updates_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
    safety_alerts_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
    preferred_locale: Absent[Optional[str]] = MISSING,
    premium_progress_bar_enabled: Absent[Optional[bool]] = False,
    # ToDo: Fill in guild features. No idea how this works - https://discord.com/developers/docs/resources/guild#guild-object-guild-features
    features: Absent[Optional[list[str]]] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> None:
    """
    Edit the guild.

    Args:
        name: The new name of the guild.
        description: The new description of the guild.
        verification_level: The new verification level for the guild.
        default_message_notifications: The new notification level for the guild.
        explicit_content_filter: The new explicit content filter level for the guild.
        afk_channel: The voice channel that should be the new AFK channel.
        afk_timeout: How many seconds does a member need to be afk before they get moved to the AFK channel. Must be either `60`, `300`, `900`, `1800` or `3600`, otherwise HTTPException will be raised.
        icon: The new icon. Requires a bytes like object or a path to an image.
        owner: The new owner of the guild. You, the bot, need to be owner for this to work.
        splash: The new invite splash image. Requires a bytes like object or a path to an image.
        discovery_splash: The new discovery image. Requires a bytes like object or a path to an image.
        banner: The new banner image. Requires a bytes like object or a path to an image.
        system_channel: The text channel where new system messages should appear. This includes boosts and welcome messages.
        system_channel_flags: The new settings for the system channel.
        rules_channel: The text channel where your rules and community guidelines are displayed.
        public_updates_channel: The text channel where updates from discord should appear.
        safety_alerts_channel: The text channel where safety alerts from discord should appear.
        preferred_locale: The new preferred locale of the guild. Must be an ISO 639 code.
        premium_progress_bar_enabled: The status of the Nitro boost bar.
        features: The enabled guild features
        reason: An optional reason for the audit log.

    """
    await self._client.http.modify_guild(
        guild_id=self.id,
        name=name,
        description=description,
        verification_level=int(verification_level) if verification_level else MISSING,
        default_message_notifications=(
            int(default_message_notifications) if default_message_notifications else MISSING
        ),
        explicit_content_filter=int(explicit_content_filter) if explicit_content_filter else MISSING,
        afk_channel_id=to_snowflake(afk_channel) if afk_channel else MISSING,
        afk_timeout=afk_timeout,
        icon=to_image_data(icon) if icon else MISSING,
        owner_id=to_snowflake(owner) if owner else MISSING,
        splash=to_image_data(splash) if splash else MISSING,
        discovery_splash=to_image_data(discovery_splash) if discovery_splash else MISSING,
        banner=to_image_data(banner) if banner else MISSING,
        system_channel_id=to_snowflake(system_channel) if system_channel else MISSING,
        system_channel_flags=int(system_channel_flags) if system_channel_flags else MISSING,
        rules_channel_id=to_snowflake(rules_channel) if rules_channel else MISSING,
        public_updates_channel_id=to_snowflake(public_updates_channel) if public_updates_channel else MISSING,
        safety_alerts_channel_id=to_snowflake(safety_alerts_channel) if safety_alerts_channel else MISSING,
        preferred_locale=preferred_locale,
        premium_progress_bar_enabled=premium_progress_bar_enabled if premium_progress_bar_enabled else False,
        features=features,
        reason=reason,
    )

edit_nickname(new_nickname=MISSING, reason=MISSING) async

Alias for me.edit_nickname

Parameters:

Name Type Description Default
new_nickname Absent[str]

The new nickname to apply

MISSING
reason Absent[str]

The reason for this change

MISSING

Note

Leave new_nickname empty to clean user's nickname

Source code in interactions/models/discord/guild.py
608
609
610
611
612
613
614
615
616
617
618
619
620
async def edit_nickname(self, new_nickname: Absent[str] = MISSING, reason: Absent[str] = MISSING) -> None:
    """
    Alias for me.edit_nickname

    Args:
        new_nickname: The new nickname to apply
        reason: The reason for this change

    !!! note
        Leave `new_nickname` empty to clean user's nickname

    """
    await self.me.edit_nickname(new_nickname, reason=reason)

estimate_prune_members(days=7, roles=MISSING) async

Calculate how many members would be pruned, should guild.prune_members be used. By default, members with roles are excluded from pruning, to include them, pass their role (or role id) in roles.

Parameters:

Name Type Description Default
days int

number of days to prune (1-30)

7
roles List[Union[Snowflake_Type, Role]]

list of roles to include in the prune

MISSING

Returns:

Type Description
int

Total number of members that would be pruned

Source code in interactions/models/discord/guild.py
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
async def estimate_prune_members(
    self, days: int = 7, roles: List[Union[Snowflake_Type, "models.Role"]] = MISSING
) -> int:
    """
    Calculate how many members would be pruned, should `guild.prune_members` be used. By default, members with roles are excluded from pruning, to include them, pass their role (or role id) in `roles`.

    Args:
        days: number of days to prune (1-30)
        roles: list of roles to include in the prune

    Returns:
        Total number of members that would be pruned

    """
    if roles is not MISSING:
        roles = [r.id if isinstance(r, models.Role) else r for r in roles]
    else:
        roles = []

    resp = await self._client.http.get_guild_prune_count(self.id, days=days, include_roles=roles)
    return resp["pruned"]

fetch_active_threads() async

Fetches all active threads in the guild, including public and private threads. Threads are ordered by their id, in descending order.

Returns:

Type Description
ThreadList

List of active threads and thread member object for each returned thread the bot user has joined.

Source code in interactions/models/discord/guild.py
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
async def fetch_active_threads(self) -> "models.ThreadList":
    """
    Fetches all active threads in the guild, including public and private threads. Threads are ordered by their id, in descending order.

    Returns:
        List of active threads and thread member object for each returned thread the bot user has joined.

    """
    threads_data = await self._client.http.list_active_threads(self.id)
    return models.ThreadList.from_dict(threads_data, self._client)

fetch_all_custom_emojis() async

Gets all the custom emoji present for this guild.

Returns:

Type Description
List[CustomEmoji]

A list of custom emoji objects.

Source code in interactions/models/discord/guild.py
916
917
918
919
920
921
922
923
924
925
async def fetch_all_custom_emojis(self) -> List["models.CustomEmoji"]:
    """
    Gets all the custom emoji present for this guild.

    Returns:
        A list of custom emoji objects.

    """
    emojis_data = await self._client.http.get_all_guild_emoji(self.id)
    return [self._client.cache.place_emoji_data(self.id, emoji_data) for emoji_data in emojis_data]

fetch_all_custom_stickers() async

Fetches all custom stickers for a guild.

Returns:

Type Description
List[Sticker]

List of Sticker objects

Source code in interactions/models/discord/guild.py
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
async def fetch_all_custom_stickers(self) -> List["models.Sticker"]:
    """
    Fetches all custom stickers for a guild.

    Returns:
        List of Sticker objects

    """
    stickers_data = await self._client.http.list_guild_stickers(self.id)
    return models.Sticker.from_list(stickers_data, self._client)

fetch_all_webhooks() async

Fetches all the webhooks for this guild.

Returns:

Type Description
List[Webhook]

A list of webhook objects.

Source code in interactions/models/discord/guild.py
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
async def fetch_all_webhooks(self) -> List["models.Webhook"]:
    """
    Fetches all the webhooks for this guild.

    Returns:
        A list of webhook objects.

    """
    webhooks_data = await self._client.http.get_guild_webhooks(self.id)
    return models.Webhook.from_list(webhooks_data, self._client)

fetch_app_cmd_perms() async

Fetch the application command permissions for this guild.

Returns:

Type Description
dict[Snowflake_Type, CommandPermissions]

The application command permissions for this guild.

Source code in interactions/models/discord/guild.py
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
async def fetch_app_cmd_perms(self) -> dict[Snowflake_Type, "CommandPermissions"]:
    """
    Fetch the application command permissions for this guild.

    Returns:
        The application command permissions for this guild.

    """
    data = await self._client.http.batch_get_application_command_permissions(self._client.app.id, self.id)

    for command in data:
        command_permissions = CommandPermissions(client=self._client, command_id=command["id"], guild=self)
        perms = [ApplicationCommandPermission.from_dict(perm, self._client) for perm in command["permissions"]]

        command_permissions.update_permissions(*perms)

        self.command_permissions[int(command["id"])] = command_permissions

    return self.command_permissions

fetch_audit_log(user_id=MISSING, action_type=MISSING, before=MISSING, after=MISSING, limit=100) async

Fetch section of the audit log for this guild.

Parameters:

Name Type Description Default
user_id Optional[Snowflake_Type]

The ID of the user to filter by

MISSING
action_type Optional[AuditLogEventType]

The type of action to filter by

MISSING
before Optional[Snowflake_Type]

The ID of the entry to start at

MISSING
after Optional[Snowflake_Type]

The ID of the entry to end at

MISSING
limit int

The number of entries to return

100

Returns:

Type Description
AuditLog

An AuditLog object

Source code in interactions/models/discord/guild.py
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
async def fetch_audit_log(
    self,
    user_id: Optional["Snowflake_Type"] = MISSING,
    action_type: Optional["AuditLogEventType"] = MISSING,
    before: Optional["Snowflake_Type"] = MISSING,
    after: Optional["Snowflake_Type"] = MISSING,
    limit: int = 100,
) -> "AuditLog":
    """
    Fetch section of the audit log for this guild.

    Args:
        user_id: The ID of the user to filter by
        action_type: The type of action to filter by
        before: The ID of the entry to start at
        after: The ID of the entry to end at
        limit: The number of entries to return

    Returns:
        An AuditLog object

    """
    data = await self._client.http.get_audit_log(self.id, user_id, action_type, before, after, limit)
    return AuditLog.from_dict(data, self._client)

fetch_auto_moderation_rules() async

Get this guild's auto moderation rules.

Returns:

Type Description
List[AutoModRule]

A list of auto moderation rules

Source code in interactions/models/discord/guild.py
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
async def fetch_auto_moderation_rules(self) -> List[AutoModRule]:
    """
    Get this guild's auto moderation rules.

    Returns:
        A list of auto moderation rules

    """
    data = await self._client.http.get_auto_moderation_rules(self.id)
    return [AutoModRule.from_dict(d, self._client) for d in data]

fetch_ban(user) async

Fetches the ban information for the specified user in the guild. You must have the ban members permission.

Parameters:

Name Type Description Default
user Union[User, Member, Snowflake_Type]

The user to look up.

required

Returns:

Type Description
Optional[GuildBan]

The ban information. If the user is not banned, returns None.

Source code in interactions/models/discord/guild.py
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
async def fetch_ban(self, user: Union["models.User", "models.Member", Snowflake_Type]) -> Optional[GuildBan]:
    """
    Fetches the ban information for the specified user in the guild. You must have the `ban members` permission.

    Args:
        user: The user to look up.

    Returns:
        The ban information. If the user is not banned, returns None.

    """
    try:
        ban_info = await self._client.http.get_guild_ban(self.id, to_snowflake(user))
    except NotFound:
        return None
    return GuildBan(reason=ban_info["reason"], user=self._client.cache.place_user_data(ban_info["user"]))

fetch_bans(before=MISSING, after=MISSING, limit=1000) async

Fetches bans for the guild. You must have the ban members permission.

Parameters:

Name Type Description Default
before Optional[Snowflake_Type]

consider only users before given user id

MISSING
after Optional[Snowflake_Type]

consider only users after given user id

MISSING
limit int

number of users to return (up to maximum 1000)

1000

Returns:

Type Description
list[GuildBan]

A list containing bans and information about them.

Source code in interactions/models/discord/guild.py
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
async def fetch_bans(
    self,
    before: Optional["Snowflake_Type"] = MISSING,
    after: Optional["Snowflake_Type"] = MISSING,
    limit: int = 1000,
) -> list[GuildBan]:
    """
    Fetches bans for the guild. You must have the `ban members` permission.

    Args:
        before: consider only users before given user id
        after: consider only users after given user id
        limit: number of users to return (up to maximum 1000)

    Returns:
        A list containing bans and information about them.

    """
    ban_infos = await self._client.http.get_guild_bans(self.id, before=before, after=after, limit=limit)
    return [
        GuildBan(reason=ban_info["reason"], user=self._client.cache.place_user_data(ban_info["user"]))
        for ban_info in ban_infos
    ]

fetch_channel(channel_id, *, force=False) async

Returns a channel with the given channel_id from the API.

Parameters:

Name Type Description Default
channel_id Snowflake_Type

The ID of the channel to get

required
force bool

Whether to force a fetch from the API

False

Returns:

Type Description
Optional[TYPE_GUILD_CHANNEL]

The channel object. If the channel does not exist, returns None.

Source code in interactions/models/discord/guild.py
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
async def fetch_channel(
    self, channel_id: Snowflake_Type, *, force: bool = False
) -> Optional["models.TYPE_GUILD_CHANNEL"]:
    """
    Returns a channel with the given `channel_id` from the API.

    Args:
        channel_id: The ID of the channel to get
        force: Whether to force a fetch from the API

    Returns:
        The channel object. If the channel does not exist, returns None.

    """
    channel_id = to_snowflake(channel_id)
    if channel_id in self._channel_ids or not self._client.gateway_started:
        # The latter check here is to see if the bot is running with the gateway.
        # If not, then we need to check the API since only the gateway
        # populates the channel IDs

        # theoretically, this could get any channel the client can see,
        # but to make it less confusing to new programmers,
        # i intentionally check that the guild contains the channel first
        try:
            channel = await self._client.fetch_channel(channel_id, force=force)
            if channel._guild_id == self.id:
                return channel
        except (NotFound, AttributeError):
            return None

    return None

fetch_channels() async

Fetch this guild's channels.

Returns:

Type Description
List[TYPE_GUILD_CHANNEL]

A list of channels in this guild

Source code in interactions/models/discord/guild.py
561
562
563
564
565
566
567
568
569
570
async def fetch_channels(self) -> List["models.TYPE_GUILD_CHANNEL"]:
    """
    Fetch this guild's channels.

    Returns:
        A list of channels in this guild

    """
    data = await self._client.http.get_guild_channels(self.id)
    return [self._client.cache.place_channel_data(channel_data) for channel_data in data]

fetch_custom_emoji(emoji_id, *, force=False) async

Fetches the custom emoji present for this guild, based on the emoji id.

Parameters:

Name Type Description Default
emoji_id Snowflake_Type

The target emoji to get data of.

required
force bool

Whether to force fetch the emoji from the API.

False

Returns:

Type Description
Optional[CustomEmoji]

The custom emoji object. If the emoji is not found, returns None.

Source code in interactions/models/discord/guild.py
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
async def fetch_custom_emoji(
    self, emoji_id: Snowflake_Type, *, force: bool = False
) -> Optional["models.CustomEmoji"]:
    """
    Fetches the custom emoji present for this guild, based on the emoji id.

    Args:
        emoji_id: The target emoji to get data of.
        force: Whether to force fetch the emoji from the API.

    Returns:
        The custom emoji object. If the emoji is not found, returns None.

    """
    try:
        return await self._client.cache.fetch_emoji(self.id, emoji_id, force=force)
    except NotFound:
        return None

fetch_custom_sticker(sticker_id) async

Fetches a specific custom sticker for a guild.

Parameters:

Name Type Description Default
sticker_id Snowflake_Type

ID of sticker to get

required

Returns:

Type Description
Optional[Sticker]

The custom sticker object. If the sticker does not exist, returns None.

Source code in interactions/models/discord/guild.py
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
async def fetch_custom_sticker(self, sticker_id: Snowflake_Type) -> Optional["models.Sticker"]:
    """
    Fetches a specific custom sticker for a guild.

    Args:
        sticker_id: ID of sticker to get

    Returns:
        The custom sticker object. If the sticker does not exist, returns None.

    """
    try:
        sticker_data = await self._client.http.get_guild_sticker(self.id, to_snowflake(sticker_id))
    except NotFound:
        return None
    return models.Sticker.from_dict(sticker_data, self._client)

fetch_guild_integrations() async

Fetches all integrations for the guild.

Returns:

Type Description
List[GuildIntegration]

A list of integrations for the guild.

Source code in interactions/models/discord/guild.py
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
async def fetch_guild_integrations(self) -> List["models.GuildIntegration"]:
    """
    Fetches all integrations for the guild.

    Returns:
        A list of integrations for the guild.

    """
    data = await self._client.http.get_guild_integrations(self.id)
    return [GuildIntegration.from_dict(d | {"guild_id": self.id}, self._client) for d in data]

fetch_guild_templates() async

Fetch all guild templates for this guild.

Returns:

Type Description
List[GuildTemplate]

A list of guild template objects.

Source code in interactions/models/discord/guild.py
905
906
907
908
909
910
911
912
913
914
async def fetch_guild_templates(self) -> List["models.GuildTemplate"]:
    """
    Fetch all guild templates for this guild.

    Returns:
        A list of guild template objects.

    """
    templates = await self._client.http.get_guild_templates(self.id)
    return GuildTemplate.from_list(templates, self._client)

fetch_invites() async

Fetches all invites for the guild.

Returns:

Type Description
List[Invite]

A list of invites for the guild.

Source code in interactions/models/discord/guild.py
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
async def fetch_invites(self) -> List["models.Invite"]:
    """
    Fetches all invites for the guild.

    Returns:
        A list of invites for the guild.

    """
    invites_data = await self._client.http.get_guild_invites(self.id)
    return models.Invite.from_list(invites_data, self._client)

fetch_member(member_id, *, force=False) async

Return the Member with the given discord ID, fetching from the API if necessary.

Parameters:

Name Type Description Default
member_id Snowflake_Type

The ID of the member.

required
force bool

Whether to force a fetch from the API.

False

Returns:

Type Description
Optional[Member]

The member object fetched. If the member is not in this guild, returns None.

Source code in interactions/models/discord/guild.py
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
async def fetch_member(self, member_id: Snowflake_Type, *, force: bool = False) -> Optional["models.Member"]:
    """
    Return the Member with the given discord ID, fetching from the API if necessary.

    Args:
        member_id: The ID of the member.
        force: Whether to force a fetch from the API.

    Returns:
        The member object fetched. If the member is not in this guild, returns None.

    """
    try:
        return await self._client.cache.fetch_member(self.id, member_id, force=force)
    except NotFound:
        return None

fetch_onboarding() async

Fetches the guild's onboarding settings.

Returns:

Type Description
Onboarding

The guild's onboarding settings.

Source code in interactions/models/discord/guild.py
2091
2092
2093
2094
2095
2096
2097
2098
2099
async def fetch_onboarding(self) -> Onboarding:
    """
    Fetches the guild's onboarding settings.

    Returns:
        The guild's onboarding settings.

    """
    return Onboarding.from_dict(await self._client.http.get_guild_onboarding(self.id), self._client)

fetch_owner(*, force=False) async

Return the Guild owner, fetching from the API if necessary.

Parameters:

Name Type Description Default
force bool

Whether to force a fetch from the API.

False

Returns:

Type Description
Member

Member object or None

Source code in interactions/models/discord/guild.py
538
539
540
541
542
543
544
545
546
547
548
549
async def fetch_owner(self, *, force: bool = False) -> "models.Member":
    """
    Return the Guild owner, fetching from the API if necessary.

    Args:
        force: Whether to force a fetch from the API.

    Returns:
        Member object or None

    """
    return await self._client.cache.fetch_member(self.id, self._owner_id, force=force)

fetch_role(role_id, *, force=False) async

Fetch the specified role by ID.

Parameters:

Name Type Description Default
role_id Snowflake_Type

The ID of the role to get

required
force bool

Whether to force a fetch from the API

False

Returns:

Type Description
Optional[Role]

The role object. If the role does not exist, returns None.

Source code in interactions/models/discord/guild.py
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
async def fetch_role(self, role_id: Snowflake_Type, *, force: bool = False) -> Optional["models.Role"]:
    """
    Fetch the specified role by ID.

    Args:
        role_id: The ID of the role to get
        force: Whether to force a fetch from the API

    Returns:
        The role object. If the role does not exist, returns None.

    """
    try:
        return await self._client.cache.fetch_role(self.id, role_id, force=force)
    except NotFound:
        return None

fetch_scheduled_event(scheduled_event_id, with_user_count=False, *, force=False) async

Fetches a scheduled event by id.

Parameters:

Name Type Description Default
scheduled_event_id Snowflake_Type

The id of the scheduled event.

required
with_user_count bool

Whether to include the user count in the response.

False
force bool

If the cache should be ignored, and the event should be fetched from the API

False

Returns:

Type Description
Optional[ScheduledEvent]

The scheduled event. If the event does not exist, returns None.

Source code in interactions/models/discord/guild.py
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
async def fetch_scheduled_event(
    self,
    scheduled_event_id: Snowflake_Type,
    with_user_count: bool = False,
    *,
    force: bool = False,
) -> Optional["models.ScheduledEvent"]:
    """
    Fetches a scheduled event by id.

    Args:
        scheduled_event_id: The id of the scheduled event.
        with_user_count: Whether to include the user count in the response.
        force: If the cache should be ignored, and the event should be fetched from the API

    Returns:
        The scheduled event. If the event does not exist, returns None.

    """
    try:
        return await self._client.cache.fetch_scheduled_event(
            self.id, scheduled_event_id, with_user_count=with_user_count, force=force
        )
    except NotFound:
        return None

fetch_thread(thread_id, *, force=False) async

Returns a Thread with the given thread_id from the API.

Parameters:

Name Type Description Default
thread_id Snowflake_Type

The ID of the thread to get

required
force bool

Whether to force a fetch from the API

False

Returns:

Type Description
Optional[TYPE_THREAD_CHANNEL]

Thread object if found, otherwise None

Source code in interactions/models/discord/guild.py
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
async def fetch_thread(
    self, thread_id: Snowflake_Type, *, force: bool = False
) -> Optional["models.TYPE_THREAD_CHANNEL"]:
    """
    Returns a Thread with the given `thread_id` from the API.

    Args:
        thread_id: The ID of the thread to get
        force: Whether to force a fetch from the API

    Returns:
        Thread object if found, otherwise None

    """
    thread_id = to_snowflake(thread_id)
    if thread_id in self._thread_ids:
        try:
            return await self._client.fetch_channel(thread_id, force=force)
        except NotFound:
            return None
    return None

fetch_voice_regions() async

Fetches the voice regions for the guild.

Unlike the Client.fetch_voice_regions method, this will returns VIP servers when the guild is VIP-enabled.

Returns:

Type Description
List[VoiceRegion]

A list of voice regions.

Source code in interactions/models/discord/guild.py
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
async def fetch_voice_regions(self) -> List["models.VoiceRegion"]:
    """
    Fetches the voice regions for the guild.

    Unlike the `Client.fetch_voice_regions` method, this will returns VIP servers when the guild is VIP-enabled.

    Returns:
        A list of voice regions.

    """
    regions_data = await self._client.http.get_guild_voice_regions(self.id)
    return models.VoiceRegion.from_list(regions_data)

fetch_widget() async

Fetches the guilds widget.

Returns:

Type Description
GuildWidget

The guilds widget object.

Source code in interactions/models/discord/guild.py
2004
2005
2006
2007
2008
2009
2010
2011
2012
async def fetch_widget(self) -> "GuildWidget":
    """
    Fetches the guilds widget.

    Returns:
        The guilds widget object.

    """
    return GuildWidget.from_dict(await self._client.http.get_guild_widget(self.id), self._client)

fetch_widget_image(style=None) async

Fetch a guilds widget image.

For a list of styles, look here: https://discord.com/developers/docs/resources/guild#get-guild-widget-image-widget-style-options

Parameters:

Name Type Description Default
style str | None

The style to use for the widget image

None

Returns:

Type Description
str

The URL of the widget image.

Source code in interactions/models/discord/guild.py
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
async def fetch_widget_image(self, style: str | None = None) -> str:
    """
    Fetch a guilds widget image.

    For a list of styles, look here: https://discord.com/developers/docs/resources/guild#get-guild-widget-image-widget-style-options

    Args:
        style: The style to use for the widget image

    Returns:
        The URL of the widget image.

    """
    return await self._client.http.get_guild_widget_image(self.id, style)

fetch_widget_settings() async

Fetches the guilds widget settings.

Returns:

Type Description
GuildWidgetSettings

The guilds widget settings object.

Source code in interactions/models/discord/guild.py
1994
1995
1996
1997
1998
1999
2000
2001
2002
async def fetch_widget_settings(self) -> "GuildWidgetSettings":
    """
    Fetches the guilds widget settings.

    Returns:
        The guilds widget settings object.

    """
    return await GuildWidgetSettings.from_dict(await self._client.http.get_guild_widget_settings(self.id))

gateway_chunk(wait=True, presences=True) async

Trigger a gateway get_members event, populating this object with members.

Parameters:

Name Type Description Default
wait bool

Wait for chunking to be completed before continuing

True
presences bool

Do you need presence data for members?

True
Source code in interactions/models/discord/guild.py
635
636
637
638
639
640
641
642
643
644
645
646
647
async def gateway_chunk(self, wait: bool = True, presences: bool = True) -> None:
    """
    Trigger a gateway `get_members` event, populating this object with members.

    Args:
        wait: Wait for chunking to be completed before continuing
        presences: Do you need presence data for members?

    """
    ws = self._client.get_guild_websocket(self.id)
    await ws.request_member_chunks(self.id, limit=0, presences=presences)
    if wait:
        await self.chunked.wait()

get_channel(channel_id)

Returns a channel with the given channel_id.

Parameters:

Name Type Description Default
channel_id Snowflake_Type

The ID of the channel to get

required

Returns:

Type Description
Optional[TYPE_GUILD_CHANNEL]

Channel object if found, otherwise None

Source code in interactions/models/discord/guild.py
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
def get_channel(self, channel_id: Snowflake_Type) -> Optional["models.TYPE_GUILD_CHANNEL"]:
    """
    Returns a channel with the given `channel_id`.

    Args:
        channel_id: The ID of the channel to get

    Returns:
        Channel object if found, otherwise None

    """
    channel_id = to_snowflake(channel_id)
    if channel_id in self._channel_ids:
        # theoretically, this could get any channel the client can see,
        # but to make it less confusing to new programmers,
        # i intentionally check that the guild contains the channel first
        return self._client.cache.get_channel(channel_id)
    return None

get_channel_gui_position(channel_id)

Get a given channels gui position.

Parameters:

Name Type Description Default
channel_id Snowflake_Type

The ID of the channel to get the gui position for.

required

Returns:

Type Description
int

The gui position of the channel.

Source code in interactions/models/discord/guild.py
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
def get_channel_gui_position(self, channel_id: "Snowflake_Type") -> int:
    """
    Get a given channels gui position.

    Args:
        channel_id: The ID of the channel to get the gui position for.

    Returns:
        The gui position of the channel.

    """
    if not self._channel_gui_positions:
        self._calculate_gui_channel_positions()
    return self._channel_gui_positions.get(to_snowflake(channel_id), 0)

get_custom_emoji(emoji_id)

Gets the custom emoji present for this guild, based on the emoji id.

Parameters:

Name Type Description Default
emoji_id Snowflake_Type

The target emoji to get data of.

required

Returns:

Type Description
Optional[CustomEmoji]

The custom emoji object.

Source code in interactions/models/discord/guild.py
946
947
948
949
950
951
952
953
954
955
956
957
958
959
def get_custom_emoji(self, emoji_id: Snowflake_Type) -> Optional["models.CustomEmoji"]:
    """
    Gets the custom emoji present for this guild, based on the emoji id.

    Args:
        emoji_id: The target emoji to get data of.

    Returns:
        The custom emoji object.

    """
    emoji_id = to_snowflake(emoji_id)
    emoji = self._client.cache.get_emoji(emoji_id)
    return emoji if emoji and emoji._guild_id == self.id else None

get_member(member_id)

Return the Member with the given discord ID.

Parameters:

Name Type Description Default
member_id Snowflake_Type

The ID of the member

required

Returns:

Type Description
Optional[Member]

Member object or None

Source code in interactions/models/discord/guild.py
525
526
527
528
529
530
531
532
533
534
535
536
def get_member(self, member_id: Snowflake_Type) -> Optional["models.Member"]:
    """
    Return the Member with the given discord ID.

    Args:
        member_id: The ID of the member

    Returns:
        Member object or None

    """
    return self._client.cache.get_member(self.id, member_id)

get_owner()

Return the Guild owner

Returns:

Type Description
Member

Member object or None

Source code in interactions/models/discord/guild.py
551
552
553
554
555
556
557
558
559
def get_owner(self) -> "models.Member":
    """
    Return the Guild owner

    Returns:
        Member object or None

    """
    return self._client.cache.get_member(self.id, self._owner_id)

get_role(role_id)

Get the specified role by ID.

Parameters:

Name Type Description Default
role_id Snowflake_Type

The ID of the role to get

required

Returns:

Type Description
Optional[Role]

A role object or None if the role is not found.

Source code in interactions/models/discord/guild.py
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
def get_role(self, role_id: Snowflake_Type) -> Optional["models.Role"]:
    """
    Get the specified role by ID.

    Args:
        role_id: The ID of the role to get

    Returns:
        A role object or None if the role is not found.

    """
    role_id = to_snowflake(role_id)
    if role_id in self._role_ids:
        return self._client.cache.get_role(role_id)
    return None

get_scheduled_event(scheduled_event_id)

Gets a scheduled event from the cache by id.

Parameters:

Name Type Description Default
scheduled_event_id Snowflake_Type

The id of the scheduled event.

required

Returns:

Type Description
Optional[ScheduledEvent]

The scheduled event. If the event does not exist, returns None.

Source code in interactions/models/discord/guild.py
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
def get_scheduled_event(self, scheduled_event_id: Snowflake_Type) -> Optional["models.ScheduledEvent"]:
    """
    Gets a scheduled event from the cache by id.

    Args:
        scheduled_event_id: The id of the scheduled event.

    Returns:
        The scheduled event. If the event does not exist, returns None.

    """
    event = self._client.cache.get_scheduled_event(scheduled_event_id)
    return None if event and int(event._guild_id) != self.id else event

get_thread(thread_id)

Returns a Thread with the given thread_id.

Parameters:

Name Type Description Default
thread_id Snowflake_Type

The ID of the thread to get

required

Returns:

Type Description
Optional[TYPE_THREAD_CHANNEL]

Thread object if found, otherwise None

Source code in interactions/models/discord/guild.py
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
def get_thread(self, thread_id: Snowflake_Type) -> Optional["models.TYPE_THREAD_CHANNEL"]:
    """
    Returns a Thread with the given `thread_id`.

    Args:
        thread_id: The ID of the thread to get

    Returns:
        Thread object if found, otherwise None

    """
    thread_id = to_snowflake(thread_id)
    if thread_id in self._thread_ids:
        return self._client.cache.get_channel(thread_id)
    return None

http_chunk() async

Populates all members of this guild using the REST API.

Source code in interactions/models/discord/guild.py
622
623
624
625
626
627
628
629
630
631
632
633
async def http_chunk(self) -> None:
    """Populates all members of this guild using the REST API."""
    start_time = time.perf_counter()

    iterator = MemberIterator(self)
    async for member in iterator:
        self._client.cache.place_member_data(self.id, member)

    self.chunked.set()
    self.logger.info(
        f"Cached {iterator.total_retrieved} members for {self.id} in {time.perf_counter() - start_time:.2f} seconds"
    )

is_owner(user)

Whether the user is owner of the guild.

Parameters:

Name Type Description Default
user Snowflake_Type

The user to check

required

Returns:

Type Description
bool

True if the user is the owner of the guild, False otherwise.

Note

the user argument can be any type that meets Snowflake_Type

Source code in interactions/models/discord/guild.py
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
def is_owner(self, user: Snowflake_Type) -> bool:
    """
    Whether the user is owner of the guild.

    Args:
        user: The user to check

    Returns:
        True if the user is the owner of the guild, False otherwise.

    !!! note
        the `user` argument can be any type that meets `Snowflake_Type`

    """
    return self._owner_id == to_snowflake(user)

kick(user, reason=MISSING) async

Kick a user from the guild.

Note

You must have the kick members permission

Parameters:

Name Type Description Default
user Union[User, Member, Snowflake_Type]

The user to kick

required
reason Absent[str]

The reason for the kick

MISSING
Source code in interactions/models/discord/guild.py
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
async def kick(
    self,
    user: Union["models.User", "models.Member", Snowflake_Type],
    reason: Absent[str] = MISSING,
) -> None:
    """
    Kick a user from the guild.

    !!! note
        You must have the `kick members` permission

    Args:
        user: The user to kick
        reason: The reason for the kick

    """
    await self._client.http.remove_guild_member(self.id, to_snowflake(user), reason=reason)

leave() async

Leave this guild.

Source code in interactions/models/discord/guild.py
1719
1720
1721
async def leave(self) -> None:
    """Leave this guild."""
    await self._client.http.leave_guild(self.id)

list_scheduled_events(with_user_count=False) async

List all scheduled events in this guild.

Returns:

Type Description
List[ScheduledEvent]

A list of scheduled events.

Source code in interactions/models/discord/guild.py
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
async def list_scheduled_events(self, with_user_count: bool = False) -> List["models.ScheduledEvent"]:
    """
    List all scheduled events in this guild.

    Returns:
        A list of scheduled events.

    """
    scheduled_events_data = await self._client.http.list_schedules_events(self.id, with_user_count)
    return [self._client.cache.place_scheduled_event_data(data) for data in scheduled_events_data]

modify_auto_moderation_rule(rule, *, name=MISSING, trigger=MISSING, trigger_type=MISSING, trigger_metadata=MISSING, actions=MISSING, exempt_channels=MISSING, exempt_roles=MISSING, event_type=MISSING, enabled=MISSING, reason=MISSING) async

Modify an existing automod rule.

Parameters:

Name Type Description Default
rule Snowflake_Type

The rule to modify

required
name Absent[str]

The name of the rule

MISSING
trigger Absent[BaseTrigger]

A trigger for this rule

MISSING
trigger_type Absent[AutoModTriggerType]

The type trigger for this rule (ignored if trigger specified)

MISSING
trigger_metadata Absent[dict]

Metadata for the trigger (ignored if trigger specified)

MISSING
actions Absent[list[BaseAction]]

A list of actions to take upon triggering

MISSING
exempt_roles Absent[list[Snowflake_Type]]

Roles that ignore this rule

MISSING
exempt_channels Absent[list[Snowflake_Type]]

Channels that ignore this role

MISSING
enabled Absent[bool]

Is this rule enabled?

MISSING
event_type Absent[AutoModEvent]

The type of event that triggers this rule

MISSING
reason Absent[str]

The reason for this change

MISSING

Returns:

Type Description
AutoModRule

The updated rule

Source code in interactions/models/discord/guild.py
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
async def modify_auto_moderation_rule(
    self,
    rule: "Snowflake_Type",
    *,
    name: Absent[str] = MISSING,
    trigger: Absent[BaseTrigger] = MISSING,
    trigger_type: Absent[AutoModTriggerType] = MISSING,
    trigger_metadata: Absent[dict] = MISSING,
    actions: Absent[list[BaseAction]] = MISSING,
    exempt_channels: Absent[list["Snowflake_Type"]] = MISSING,
    exempt_roles: Absent[list["Snowflake_Type"]] = MISSING,
    event_type: Absent[AutoModEvent] = MISSING,
    enabled: Absent[bool] = MISSING,
    reason: Absent[str] = MISSING,
) -> AutoModRule:
    """
    Modify an existing automod rule.

    Args:
        rule: The rule to modify
        name: The name of the rule
        trigger: A trigger for this rule
        trigger_type: The type trigger for this rule (ignored if trigger specified)
        trigger_metadata: Metadata for the trigger (ignored if trigger specified)
        actions: A list of actions to take upon triggering
        exempt_roles: Roles that ignore this rule
        exempt_channels: Channels that ignore this role
        enabled: Is this rule enabled?
        event_type: The type of event that triggers this rule
        reason: The reason for this change

    Returns:
        The updated rule

    """
    if trigger:
        _data = trigger.to_dict()
        trigger_type = _data["trigger_type"]
        trigger_metadata = _data.get("trigger_metadata", {})

    out = await self._client.http.modify_auto_moderation_rule(
        self.id,
        to_snowflake(rule),
        name=name,
        trigger_type=trigger_type,
        trigger_metadata=trigger_metadata,
        actions=actions,
        exempt_roles=to_snowflake_list(exempt_roles) if exempt_roles is not MISSING else MISSING,
        exempt_channels=to_snowflake_list(exempt_channels) if exempt_channels is not MISSING else MISSING,
        event_type=event_type,
        enabled=enabled,
        reason=reason,
    )
    return AutoModRule.from_dict(out, self._client)

modify_widget(enabled=MISSING, channel=MISSING, settings=MISSING) async

Modify the guild's widget.

Parameters:

Name Type Description Default
enabled Absent[bool]

Should the widget be enabled?

MISSING
channel Absent[Union[TYPE_GUILD_CHANNEL, Snowflake_Type]]

The channel to use in the widget

MISSING
settings Absent[GuildWidgetSettings]

The settings to use for the widget

MISSING

Returns:

Type Description
GuildWidget

The updated guilds widget object.

Source code in interactions/models/discord/guild.py
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
async def modify_widget(
    self,
    enabled: Absent[bool] = MISSING,
    channel: Absent[Union["models.TYPE_GUILD_CHANNEL", Snowflake_Type]] = MISSING,
    settings: Absent["GuildWidgetSettings"] = MISSING,
) -> "GuildWidget":
    """
    Modify the guild's widget.

    Args:
        enabled: Should the widget be enabled?
        channel: The channel to use in the widget
        settings: The settings to use for the widget

    Returns:
        The updated guilds widget object.

    """
    if isinstance(settings, GuildWidgetSettings):
        enabled = settings.enabled
        channel = settings.channel_id

    channel = to_optional_snowflake(channel)
    return GuildWidget.from_dict(
        await self._client.http.modify_guild_widget(self.id, enabled, channel), self._client
    )

process_member_chunk(chunk) async

Receive and either cache or process the chunks of members from gateway.

Parameters:

Name Type Description Default
chunk dict

A member chunk from discord

required
Source code in interactions/models/discord/guild.py
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
async def process_member_chunk(self, chunk: dict) -> None:
    """
    Receive and either cache or process the chunks of members from gateway.

    Args:
        chunk: A member chunk from discord

    """
    if self.chunked.is_set():
        self.chunked.clear()

    if presences := chunk.get("presences"):
        # combine the presence dict into the members dict
        for presence in presences:
            u_id = presence["user"]["id"]
            # find the user this presence is for
            member_index = next(
                (index for (index, d) in enumerate(chunk.get("members")) if d["user"]["id"] == u_id),
                None,
            )
            del presence["user"]
            chunk["members"][member_index]["user"] = chunk["members"][member_index]["user"] | presence

    self._chunk_cache = self._chunk_cache + chunk.get("members") if self._chunk_cache else chunk.get("members")
    if chunk.get("chunk_index") != chunk.get("chunk_count") - 1:
        return self.logger.debug(f"Cached chunk of {len(chunk.get('members'))} members for {self.id}")
    members = self._chunk_cache
    self.logger.info(f"Processing {len(members)} members for {self.id}")

    s = time.monotonic()
    start_time = time.perf_counter()

    for member in members:
        self._client.cache.place_member_data(self.id, member)
        if (time.monotonic() - s) > 0.05:
            # look, i get this *could* be a thread, but because it needs to modify data in the main thread,
            # it is still blocking. So by periodically yielding to the event loop, we can avoid blocking, and still
            # process this data properly
            await asyncio.sleep(0)
            s = time.monotonic()

    total_time = time.perf_counter() - start_time
    self.chunk_cache = []
    self.logger.info(f"Cached members for {self.id} in {total_time:.2f} seconds")
    self.chunked.set()

prune_members(days=7, roles=None, compute_prune_count=True, reason=MISSING) async

Begin a guild prune. Removes members from the guild who who have not interacted for the last days days. By default, members with roles are excluded from pruning, to include them, pass their role (or role id) in roles Requires kick members permission.

Parameters:

Name Type Description Default
days int

number of days to prune (1-30)

7
roles Optional[List[Snowflake_Type]]

list of roles to include in the prune

None
compute_prune_count bool

Whether the number of members pruned should be calculated (disable this for large guilds)

True
reason Absent[str]

The reason for this prune

MISSING

Returns:

Type Description
Optional[int]

The total number of members pruned, if compute_prune_count is set to True, otherwise None

Source code in interactions/models/discord/guild.py
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
async def prune_members(
    self,
    days: int = 7,
    roles: Optional[List[Snowflake_Type]] = None,
    compute_prune_count: bool = True,
    reason: Absent[str] = MISSING,
) -> Optional[int]:
    """
    Begin a guild prune. Removes members from the guild who who have not interacted for the last `days` days. By default, members with roles are excluded from pruning, to include them, pass their role (or role id) in `roles` Requires `kick members` permission.

    Args:
        days: number of days to prune (1-30)
        roles: list of roles to include in the prune
        compute_prune_count: Whether the number of members pruned should be calculated (disable this for large guilds)
        reason: The reason for this prune

    Returns:
        The total number of members pruned, if `compute_prune_count` is set to True, otherwise None

    """
    if roles:
        roles = [str(to_snowflake(r)) for r in roles]

    resp = await self._client.http.begin_guild_prune(
        self.id,
        days,
        include_roles=roles,
        compute_prune_count=compute_prune_count,
        reason=reason,
    )
    return resp["pruned"]

search_members(query, limit=1) async

Search for members in the guild whose username or nickname starts with a provided string.

Parameters:

Name Type Description Default
query str

Query string to match username(s) and nickname(s) against.

required
limit int

Max number of members to return (1-1000)

1

Returns:

Type Description
List[Member]

A list of members matching the query.

Source code in interactions/models/discord/guild.py
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
async def search_members(self, query: str, limit: int = 1) -> List["models.Member"]:
    """
    Search for members in the guild whose username or nickname starts with a provided string.

    Args:
        query: Query string to match username(s) and nickname(s) against.
        limit: Max number of members to return (1-1000)

    Returns:
        A list of members matching the query.

    """
    data = await self._client.http.search_guild_members(guild_id=self.id, query=query, limit=limit)
    return [self._client.cache.place_member_data(self.id, _d) for _d in data]

unban(user, reason=MISSING) async

Unban a user from the guild.

Note

You must have the ban members permission

Parameters:

Name Type Description Default
user Union[User, Member, Snowflake_Type]

The user to unban

required
reason Absent[str]

The reason for the ban

MISSING
Source code in interactions/models/discord/guild.py
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
async def unban(
    self,
    user: Union["models.User", "models.Member", Snowflake_Type],
    reason: Absent[str] = MISSING,
) -> None:
    """
    Unban a user from the guild.

    !!! note
        You must have the `ban members` permission

    Args:
        user: The user to unban
        reason: The reason for the ban

    """
    await self._client.http.remove_guild_ban(self.id, to_snowflake(user), reason=reason)

GuildBan

Source code in interactions/models/discord/guild.py
81
82
83
84
85
86
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class GuildBan:
    reason: Optional[str]
    """The reason for the ban"""
    user: "models.User"
    """The banned user"""

reason: Optional[str] class-attribute

The reason for the ban

user: models.User class-attribute

The banned user

GuildIntegration

Bases: DiscordObject

Source code in interactions/models/discord/guild.py
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
class GuildIntegration(DiscordObject):
    name: str = attrs.field(repr=True)
    """The name of the integration"""
    type: str = attrs.field(repr=True)
    """integration type (twitch, youtube, or discord)"""
    enabled: bool = attrs.field(repr=True)
    """is this integration enabled"""
    account: dict = attrs.field(
        repr=False,
    )
    """integration account information"""
    application: Optional["models.Application"] = attrs.field(repr=False, default=None)
    """The bot/OAuth2 application for discord integrations"""
    _guild_id: Snowflake_Type = attrs.field(
        repr=False,
    )

    syncing: Optional[bool] = attrs.field(repr=False, default=MISSING)
    """is this integration syncing"""
    role_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=MISSING)
    """id that this integration uses for "subscribers\""""
    enable_emoticons: bool = attrs.field(repr=False, default=MISSING)
    """whether emoticons should be synced for this integration (twitch only currently)"""
    expire_behavior: IntegrationExpireBehaviour = attrs.field(
        repr=False, default=MISSING, converter=optional(IntegrationExpireBehaviour)
    )
    """the behavior of expiring subscribers"""
    expire_grace_period: int = attrs.field(repr=False, default=MISSING)
    """the grace period (in days) before expiring subscribers"""
    user: "models.BaseUser" = attrs.field(repr=False, default=MISSING)
    """user for this integration"""
    synced_at: "models.Timestamp" = attrs.field(repr=False, default=MISSING, converter=optional(timestamp_converter))
    """when this integration was last synced"""
    subscriber_count: int = attrs.field(repr=False, default=MISSING)
    """how many subscribers this integration has"""
    revoked: bool = attrs.field(repr=False, default=MISSING)
    """has this integration been revoked"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if app := data.get("application", None):
            data["application"] = models.Application.from_dict(app, client)
        if user := data.get("user", None):
            data["user"] = client.cache.place_user_data(user)

        return data

    async def delete(self, reason: Absent[str] = MISSING) -> None:
        """Delete this guild integration."""
        await self._client.http.delete_guild_integration(self._guild_id, self.id, reason)

account: dict = attrs.field(repr=False) class-attribute

integration account information

application: Optional[models.Application] = attrs.field(repr=False, default=None) class-attribute

The bot/OAuth2 application for discord integrations

enable_emoticons: bool = attrs.field(repr=False, default=MISSING) class-attribute

whether emoticons should be synced for this integration (twitch only currently)

enabled: bool = attrs.field(repr=True) class-attribute

is this integration enabled

expire_behavior: IntegrationExpireBehaviour = attrs.field(repr=False, default=MISSING, converter=optional(IntegrationExpireBehaviour)) class-attribute

the behavior of expiring subscribers

expire_grace_period: int = attrs.field(repr=False, default=MISSING) class-attribute

the grace period (in days) before expiring subscribers

name: str = attrs.field(repr=True) class-attribute

The name of the integration

revoked: bool = attrs.field(repr=False, default=MISSING) class-attribute

has this integration been revoked

role_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=MISSING) class-attribute

id that this integration uses for "subscribers"

subscriber_count: int = attrs.field(repr=False, default=MISSING) class-attribute

how many subscribers this integration has

synced_at: models.Timestamp = attrs.field(repr=False, default=MISSING, converter=optional(timestamp_converter)) class-attribute

when this integration was last synced

syncing: Optional[bool] = attrs.field(repr=False, default=MISSING) class-attribute

is this integration syncing

type: str = attrs.field(repr=True) class-attribute

integration type (twitch, youtube, or discord)

user: models.BaseUser = attrs.field(repr=False, default=MISSING) class-attribute

user for this integration

delete(reason=MISSING) async

Delete this guild integration.

Source code in interactions/models/discord/guild.py
2303
2304
2305
async def delete(self, reason: Absent[str] = MISSING) -> None:
    """Delete this guild integration."""
    await self._client.http.delete_guild_integration(self._guild_id, self.id, reason)

GuildPreview

Bases: BaseGuild

A partial guild object.

Source code in interactions/models/discord/guild.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class GuildPreview(BaseGuild):
    """A partial guild object."""

    emoji: list["models.PartialEmoji"] = attrs.field(repr=False, factory=list)
    """A list of custom emoji from this guild"""
    approximate_member_count: int = attrs.field(repr=False, default=0)
    """Approximate number of members in this guild"""
    approximate_presence_count: int = attrs.field(repr=False, default=0)
    """Approximate number of online members in this guild"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        return super()._process_dict(data, client)

approximate_member_count: int = attrs.field(repr=False, default=0) class-attribute

Approximate number of members in this guild

approximate_presence_count: int = attrs.field(repr=False, default=0) class-attribute

Approximate number of online members in this guild

emoji: list[models.PartialEmoji] = attrs.field(repr=False, factory=list) class-attribute

A list of custom emoji from this guild

GuildTemplate

Bases: ClientObject

Source code in interactions/models/discord/guild.py
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class GuildTemplate(ClientObject):
    code: str = attrs.field(repr=True, metadata=docs("the template code (unique ID)"))
    name: str = attrs.field(repr=True, metadata=docs("the name"))
    description: Optional[str] = attrs.field(repr=False, default=None, metadata=docs("the description"))

    usage_count: int = attrs.field(repr=False, default=0, metadata=docs("number of times this template has been used"))

    creator_id: Snowflake_Type = attrs.field(repr=False, metadata=docs("The ID of the user who created this template"))
    creator: Optional["models.User"] = attrs.field(
        repr=False, default=None, metadata=docs("the user who created this template")
    )

    created_at: "models.Timestamp" = attrs.field(repr=False, metadata=docs("When this template was created"))
    updated_at: "models.Timestamp" = attrs.field(
        repr=False, metadata=docs("When this template was last synced to the source guild")
    )

    source_guild_id: Snowflake_Type = attrs.field(
        repr=False, metadata=docs("The ID of the guild this template is based on")
    )
    guild_snapshot: "models.Guild" = attrs.field(
        repr=False, metadata=docs("A snapshot of the guild this template contains")
    )

    is_dirty: bool = attrs.field(
        repr=False, default=False, metadata=docs("Whether this template has un-synced changes")
    )

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data["creator"] = client.cache.place_user_data(data["creator"])

        # todo: partial guild obj that **isn't** cached
        data["guild_snapshot"] = data.pop("serialized_source_guild")
        return data

    async def synchronise(self) -> "models.GuildTemplate":
        """Synchronise the template to the source guild's current state."""
        data = await self._client.http.sync_guild_template(self.source_guild_id, self.code)
        self.update_from_dict(data)
        return self

    async def modify(self, name: Absent[str] = MISSING, description: Absent[str] = MISSING) -> "models.GuildTemplate":
        """
        Modify the template's metadata.

        Args:
            name: The name for the template
            description: The description for the template

        Returns:
            The modified template object.

        """
        data = await self._client.http.modify_guild_template(
            self.source_guild_id, self.code, name=name, description=description
        )
        self.update_from_dict(data)
        return self

    async def delete(self) -> None:
        """Delete the guild template."""
        await self._client.http.delete_guild_template(self.source_guild_id, self.code)

delete() async

Delete the guild template.

Source code in interactions/models/discord/guild.py
2237
2238
2239
async def delete(self) -> None:
    """Delete the guild template."""
    await self._client.http.delete_guild_template(self.source_guild_id, self.code)

modify(name=MISSING, description=MISSING) async

Modify the template's metadata.

Parameters:

Name Type Description Default
name Absent[str]

The name for the template

MISSING
description Absent[str]

The description for the template

MISSING

Returns:

Type Description
GuildTemplate

The modified template object.

Source code in interactions/models/discord/guild.py
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
async def modify(self, name: Absent[str] = MISSING, description: Absent[str] = MISSING) -> "models.GuildTemplate":
    """
    Modify the template's metadata.

    Args:
        name: The name for the template
        description: The description for the template

    Returns:
        The modified template object.

    """
    data = await self._client.http.modify_guild_template(
        self.source_guild_id, self.code, name=name, description=description
    )
    self.update_from_dict(data)
    return self

synchronise() async

Synchronise the template to the source guild's current state.

Source code in interactions/models/discord/guild.py
2213
2214
2215
2216
2217
async def synchronise(self) -> "models.GuildTemplate":
    """Synchronise the template to the source guild's current state."""
    data = await self._client.http.sync_guild_template(self.source_guild_id, self.code)
    self.update_from_dict(data)
    return self

GuildWidget

Bases: DiscordObject

Source code in interactions/models/discord/guild.py
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
class GuildWidget(DiscordObject):
    name: str = attrs.field(repr=True)
    """Guild name (2-100 characters)"""
    instant_invite: str = attrs.field(repr=True, default=None)
    """Instant invite for the guilds specified widget invite channel"""
    presence_count: int = attrs.field(repr=True, default=0)
    """Number of online members in this guild"""

    _channel_ids: List["Snowflake_Type"] = attrs.field(repr=False, default=[])
    """Voice and stage channels which are accessible by @everyone"""
    _member_ids: List["Snowflake_Type"] = attrs.field(repr=False, default=[])
    """Special widget user objects that includes users presence (Limit 100)"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if channels := data.get("channels"):
            data["channel_ids"] = [channel["id"] for channel in channels]
        if members := data.get("members"):
            data["member_ids"] = [member["id"] for member in members]
        return data

    def get_channels(self) -> List["models.TYPE_VOICE_CHANNEL"]:
        """
        Gets voice and stage channels which are accessible by @everyone

        Returns:
            List of channels

        """
        return [self._client.get_channel(channel_id) for channel_id in self._channel_ids]

    async def fetch_channels(self, *, force: bool = False) -> List["models.TYPE_VOICE_CHANNEL"]:
        """
        Gets voice and stage channels which are accessible by @everyone. Fetches the channels from API if they are not cached.

        Args:
            force: Whether to force fetch the channels from API

        Returns:
            List of channels

        """
        return [await self._client.fetch_channel(channel_id, force=force) for channel_id in self._channel_ids]

    def get_members(self) -> List["models.User"]:
        """
        Gets special widget user objects that includes users presence (Limit 100)

        Returns:
            List of users

        """
        return [self._client.get_user(member_id) for member_id in self._member_ids]

    async def fetch_members(self, *, force: bool = False) -> List["models.User"]:
        """
        Gets special widget user objects that includes users presence (Limit 100). Fetches the users from API if they are not cached.

        Args:
            force: Whether to force fetch the users from API

        Returns:
            List of users

        """
        return [await self._client.fetch_user(member_id, force=force) for member_id in self._member_ids]

instant_invite: str = attrs.field(repr=True, default=None) class-attribute

Instant invite for the guilds specified widget invite channel

name: str = attrs.field(repr=True) class-attribute

Guild name (2-100 characters)

presence_count: int = attrs.field(repr=True, default=0) class-attribute

Number of online members in this guild

fetch_channels(*, force=False) async

Gets voice and stage channels which are accessible by @everyone. Fetches the channels from API if they are not cached.

Parameters:

Name Type Description Default
force bool

Whether to force fetch the channels from API

False

Returns:

Type Description
List[TYPE_VOICE_CHANNEL]

List of channels

Source code in interactions/models/discord/guild.py
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
async def fetch_channels(self, *, force: bool = False) -> List["models.TYPE_VOICE_CHANNEL"]:
    """
    Gets voice and stage channels which are accessible by @everyone. Fetches the channels from API if they are not cached.

    Args:
        force: Whether to force fetch the channels from API

    Returns:
        List of channels

    """
    return [await self._client.fetch_channel(channel_id, force=force) for channel_id in self._channel_ids]

fetch_members(*, force=False) async

Gets special widget user objects that includes users presence (Limit 100). Fetches the users from API if they are not cached.

Parameters:

Name Type Description Default
force bool

Whether to force fetch the users from API

False

Returns:

Type Description
List[User]

List of users

Source code in interactions/models/discord/guild.py
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
async def fetch_members(self, *, force: bool = False) -> List["models.User"]:
    """
    Gets special widget user objects that includes users presence (Limit 100). Fetches the users from API if they are not cached.

    Args:
        force: Whether to force fetch the users from API

    Returns:
        List of users

    """
    return [await self._client.fetch_user(member_id, force=force) for member_id in self._member_ids]

get_channels()

Gets voice and stage channels which are accessible by @everyone

Returns:

Type Description
List[TYPE_VOICE_CHANNEL]

List of channels

Source code in interactions/models/discord/guild.py
2336
2337
2338
2339
2340
2341
2342
2343
2344
def get_channels(self) -> List["models.TYPE_VOICE_CHANNEL"]:
    """
    Gets voice and stage channels which are accessible by @everyone

    Returns:
        List of channels

    """
    return [self._client.get_channel(channel_id) for channel_id in self._channel_ids]

get_members()

Gets special widget user objects that includes users presence (Limit 100)

Returns:

Type Description
List[User]

List of users

Source code in interactions/models/discord/guild.py
2359
2360
2361
2362
2363
2364
2365
2366
2367
def get_members(self) -> List["models.User"]:
    """
    Gets special widget user objects that includes users presence (Limit 100)

    Returns:
        List of users

    """
    return [self._client.get_user(member_id) for member_id in self._member_ids]

GuildWidgetSettings

Bases: DictSerializationMixin

Source code in interactions/models/discord/guild.py
2308
2309
2310
2311
2312
class GuildWidgetSettings(DictSerializationMixin):
    enabled: bool = attrs.field(repr=True, default=False)
    """Whether the widget is enabled."""
    channel_id: Optional["Snowflake_Type"] = attrs.field(repr=True, default=None, converter=to_optional_snowflake)
    """The widget channel id. None if widget is not enabled."""

channel_id: Optional[Snowflake_Type] = attrs.field(repr=True, default=None, converter=to_optional_snowflake) class-attribute

The widget channel id. None if widget is not enabled.

enabled: bool = attrs.field(repr=True, default=False) class-attribute

Whether the widget is enabled.

ScheduledEvent

Bases: DiscordObject

Source code in interactions/models/discord/scheduled_event.py
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ScheduledEvent(DiscordObject):
    name: str = attrs.field(repr=True)
    description: str = attrs.field(repr=False, default=MISSING)
    entity_type: Union[ScheduledEventType, int] = attrs.field(repr=False, converter=ScheduledEventType)
    """The type of the scheduled event"""
    start_time: Timestamp = attrs.field(repr=False, converter=timestamp_converter)
    """A Timestamp object representing the scheduled start time of the event """
    end_time: Optional[Timestamp] = attrs.field(repr=False, default=None, converter=optional(timestamp_converter))
    """Optional Timstamp object representing the scheduled end time, required if entity_type is EXTERNAL"""
    privacy_level: Union[ScheduledEventPrivacyLevel, int] = attrs.field(
        repr=False, converter=ScheduledEventPrivacyLevel
    )
    """
    Privacy level of the scheduled event

    ??? note
        Discord only has `GUILD_ONLY` at the momment.
    """
    status: Union[ScheduledEventStatus, int] = attrs.field(repr=False, converter=ScheduledEventStatus)
    """Current status of the scheduled event"""
    entity_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=MISSING, converter=optional(to_snowflake))
    """The id of an entity associated with a guild scheduled event"""
    entity_metadata: Optional[Dict[str, Any]] = attrs.field(repr=False, default=MISSING)  # TODO make this
    """The metadata associated with the entity_type"""
    user_count: Absent[int] = attrs.field(repr=False, default=MISSING)  # TODO make this optional and None in 6.0
    """Amount of users subscribed to the scheduled event"""
    cover: Asset | None = attrs.field(repr=False, default=None)
    """The cover image of this event"""

    _guild_id: "Snowflake_Type" = attrs.field(repr=False, converter=to_snowflake)
    _creator: Optional["User"] = attrs.field(repr=False, default=MISSING)
    _creator_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=MISSING, converter=optional(to_snowflake))
    _channel_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None, converter=optional(to_snowflake))

    @property
    async def creator(self) -> Optional["User"]:
        """
        Returns the user who created this event.

        !!! note
            Events made before October 25th, 2021 will not have a creator.

        """
        return await self._client.cache.fetch_user(self._creator_id) if self._creator_id else None

    @property
    def guild(self) -> "Guild":
        return self._client.cache.get_guild(self._guild_id)

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if data.get("creator"):
            data["creator"] = client.cache.place_user_data(data["creator"])

        if data.get("channel_id"):
            data["channel"] = client.cache.get_channel(data["channel_id"])

        data["start_time"] = data.get("scheduled_start_time")

        if end_time := data.get("scheduled_end_time"):
            data["end_time"] = end_time
        else:
            data["end_time"] = None

        if image := data.get("image"):
            data["cover"] = Asset.from_path_hash(client, f"guild-events/{data['id']}/{{}}", image)

        data = super()._process_dict(data, client)
        return data

    @property
    def location(self) -> Optional[str]:
        """Returns the external locatian of this event."""
        if self.entity_type == ScheduledEventType.EXTERNAL:
            return self.entity_metadata["location"]
        return None

    async def fetch_channel(self, *, force: bool = False) -> Optional[Union["GuildVoice", "GuildStageVoice"]]:
        """
        Returns the channel this event is scheduled in if it is scheduled in a channel.

        Args:
            force: Whether to force fetch the channel from the API

        """
        if self._channel_id:
            return await self._client.cache.fetch_channel(self._channel_id, force=force)
        return None

    def get_channel(self) -> Optional[Union["GuildVoice", "GuildStageVoice"]]:
        """Returns the channel this event is scheduled in if it is scheduled in a channel."""
        if self._channel_id:
            return self._client.cache.get_channel(self._channel_id)
        return None

    async def fetch_event_users(
        self,
        limit: Optional[int] = 100,
        with_member_data: bool = False,
        before: Absent[Optional["Snowflake_Type"]] = MISSING,
        after: Absent[Optional["Snowflake_Type"]] = MISSING,
    ) -> List[Union["Member", "User"]]:
        """
        Fetch event users.

        Args:
            limit: Discord defualts to 100
            with_member_data: Whether to include guild member data
            before: Snowflake of a user to get before
            after: Snowflake of a user to get after

        !!! note
            This method is paginated

        """
        event_users = await self._client.http.get_scheduled_event_users(
            self._guild_id, self.id, limit, with_member_data, before, after
        )
        participants = []
        for u in event_users:
            if member := u.get("member"):
                u["member"]["user"] = u["user"]
                participants.append(self._client.cache.place_member_data(self._guild_id, member))
            else:
                participants.append(self._client.cache.place_user_data(u["user"]))

        return participants

    async def delete(self, reason: Absent[str] = MISSING) -> None:
        """
        Deletes this event.

        Args:
            reason: The reason for deleting this event

        """
        await self._client.http.delete_scheduled_event(self._guild_id, self.id, reason)

    async def edit(
        self,
        *,
        name: Absent[str] = MISSING,
        start_time: Absent["Timestamp"] = MISSING,
        end_time: Absent["Timestamp"] = MISSING,
        status: Absent[ScheduledEventStatus] = MISSING,
        description: Absent[str] = MISSING,
        channel_id: Absent[Optional["Snowflake_Type"]] = MISSING,
        event_type: Absent[ScheduledEventType] = MISSING,
        external_location: Absent[Optional[str]] = MISSING,
        entity_metadata: Absent[dict] = MISSING,
        privacy_level: Absent[ScheduledEventPrivacyLevel] = MISSING,
        cover_image: Absent[UPLOADABLE_TYPE] = MISSING,
        reason: Absent[str] = MISSING,
    ) -> None:
        """
        Edits this event.

        Args:
            name: The name of the event
            description: The description of the event
            channel_id: The channel id of the event
            event_type: The type of the event
            start_time: The scheduled start time of the event
            end_time: The scheduled end time of the event
            status: The status of the event
            external_location: The location of the event (1-100 characters)
            entity_metadata: The metadata of the event
            privacy_level: The privacy level of the event
            cover_image: the cover image of the scheduled event
            reason: The reason for editing the event

        !!! note
            If updating event_type to EXTERNAL:
                `channel_id` is required and must be set to null

                `external_location` or `entity_metadata` with a location field must be provided

                `end_time` must be provided

        """
        if external_location is not MISSING:
            entity_metadata = {"location": external_location}

        if event_type == ScheduledEventType.EXTERNAL:
            channel_id = None
            if external_location == MISSING:
                raise EventLocationNotProvided("Location is required for external events")

        payload = {
            "name": name,
            "description": description,
            "channel_id": channel_id,
            "entity_type": event_type,
            "scheduled_start_time": start_time.isoformat() if start_time else MISSING,
            "scheduled_end_time": end_time.isoformat() if end_time else MISSING,
            "status": status,
            "entity_metadata": entity_metadata,
            "privacy_level": privacy_level,
            "image": to_image_data(cover_image) if cover_image else MISSING,
        }
        await self._client.http.modify_scheduled_event(self._guild_id, self.id, payload, reason)

cover: Asset | None = attrs.field(repr=False, default=None) class-attribute

The cover image of this event

creator: Optional[User] async property

Returns the user who created this event.

Note

Events made before October 25th, 2021 will not have a creator.

end_time: Optional[Timestamp] = attrs.field(repr=False, default=None, converter=optional(timestamp_converter)) class-attribute

Optional Timstamp object representing the scheduled end time, required if entity_type is EXTERNAL

entity_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=MISSING, converter=optional(to_snowflake)) class-attribute

The id of an entity associated with a guild scheduled event

entity_metadata: Optional[Dict[str, Any]] = attrs.field(repr=False, default=MISSING) class-attribute

The metadata associated with the entity_type

entity_type: Union[ScheduledEventType, int] = attrs.field(repr=False, converter=ScheduledEventType) class-attribute

The type of the scheduled event

location: Optional[str] property

Returns the external locatian of this event.

privacy_level: Union[ScheduledEventPrivacyLevel, int] = attrs.field(repr=False, converter=ScheduledEventPrivacyLevel) class-attribute

Privacy level of the scheduled event

Note

Discord only has GUILD_ONLY at the momment.

start_time: Timestamp = attrs.field(repr=False, converter=timestamp_converter) class-attribute

A Timestamp object representing the scheduled start time of the event

status: Union[ScheduledEventStatus, int] = attrs.field(repr=False, converter=ScheduledEventStatus) class-attribute

Current status of the scheduled event

user_count: Absent[int] = attrs.field(repr=False, default=MISSING) class-attribute

Amount of users subscribed to the scheduled event

delete(reason=MISSING) async

Deletes this event.

Parameters:

Name Type Description Default
reason Absent[str]

The reason for deleting this event

MISSING
Source code in interactions/models/discord/scheduled_event.py
156
157
158
159
160
161
162
163
164
async def delete(self, reason: Absent[str] = MISSING) -> None:
    """
    Deletes this event.

    Args:
        reason: The reason for deleting this event

    """
    await self._client.http.delete_scheduled_event(self._guild_id, self.id, reason)

edit(*, name=MISSING, start_time=MISSING, end_time=MISSING, status=MISSING, description=MISSING, channel_id=MISSING, event_type=MISSING, external_location=MISSING, entity_metadata=MISSING, privacy_level=MISSING, cover_image=MISSING, reason=MISSING) async

Edits this event.

Parameters:

Name Type Description Default
name Absent[str]

The name of the event

MISSING
description Absent[str]

The description of the event

MISSING
channel_id Absent[Optional[Snowflake_Type]]

The channel id of the event

MISSING
event_type Absent[ScheduledEventType]

The type of the event

MISSING
start_time Absent[Timestamp]

The scheduled start time of the event

MISSING
end_time Absent[Timestamp]

The scheduled end time of the event

MISSING
status Absent[ScheduledEventStatus]

The status of the event

MISSING
external_location Absent[Optional[str]]

The location of the event (1-100 characters)

MISSING
entity_metadata Absent[dict]

The metadata of the event

MISSING
privacy_level Absent[ScheduledEventPrivacyLevel]

The privacy level of the event

MISSING
cover_image Absent[UPLOADABLE_TYPE]

the cover image of the scheduled event

MISSING
reason Absent[str]

The reason for editing the event

MISSING

Note

If updating event_type to EXTERNAL: channel_id is required and must be set to null

1
2
3
`external_location` or `entity_metadata` with a location field must be provided

`end_time` must be provided
Source code in interactions/models/discord/scheduled_event.py
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
async def edit(
    self,
    *,
    name: Absent[str] = MISSING,
    start_time: Absent["Timestamp"] = MISSING,
    end_time: Absent["Timestamp"] = MISSING,
    status: Absent[ScheduledEventStatus] = MISSING,
    description: Absent[str] = MISSING,
    channel_id: Absent[Optional["Snowflake_Type"]] = MISSING,
    event_type: Absent[ScheduledEventType] = MISSING,
    external_location: Absent[Optional[str]] = MISSING,
    entity_metadata: Absent[dict] = MISSING,
    privacy_level: Absent[ScheduledEventPrivacyLevel] = MISSING,
    cover_image: Absent[UPLOADABLE_TYPE] = MISSING,
    reason: Absent[str] = MISSING,
) -> None:
    """
    Edits this event.

    Args:
        name: The name of the event
        description: The description of the event
        channel_id: The channel id of the event
        event_type: The type of the event
        start_time: The scheduled start time of the event
        end_time: The scheduled end time of the event
        status: The status of the event
        external_location: The location of the event (1-100 characters)
        entity_metadata: The metadata of the event
        privacy_level: The privacy level of the event
        cover_image: the cover image of the scheduled event
        reason: The reason for editing the event

    !!! note
        If updating event_type to EXTERNAL:
            `channel_id` is required and must be set to null

            `external_location` or `entity_metadata` with a location field must be provided

            `end_time` must be provided

    """
    if external_location is not MISSING:
        entity_metadata = {"location": external_location}

    if event_type == ScheduledEventType.EXTERNAL:
        channel_id = None
        if external_location == MISSING:
            raise EventLocationNotProvided("Location is required for external events")

    payload = {
        "name": name,
        "description": description,
        "channel_id": channel_id,
        "entity_type": event_type,
        "scheduled_start_time": start_time.isoformat() if start_time else MISSING,
        "scheduled_end_time": end_time.isoformat() if end_time else MISSING,
        "status": status,
        "entity_metadata": entity_metadata,
        "privacy_level": privacy_level,
        "image": to_image_data(cover_image) if cover_image else MISSING,
    }
    await self._client.http.modify_scheduled_event(self._guild_id, self.id, payload, reason)

fetch_channel(*, force=False) async

Returns the channel this event is scheduled in if it is scheduled in a channel.

Parameters:

Name Type Description Default
force bool

Whether to force fetch the channel from the API

False
Source code in interactions/models/discord/scheduled_event.py
105
106
107
108
109
110
111
112
113
114
115
async def fetch_channel(self, *, force: bool = False) -> Optional[Union["GuildVoice", "GuildStageVoice"]]:
    """
    Returns the channel this event is scheduled in if it is scheduled in a channel.

    Args:
        force: Whether to force fetch the channel from the API

    """
    if self._channel_id:
        return await self._client.cache.fetch_channel(self._channel_id, force=force)
    return None

fetch_event_users(limit=100, with_member_data=False, before=MISSING, after=MISSING) async

Fetch event users.

Parameters:

Name Type Description Default
limit Optional[int]

Discord defualts to 100

100
with_member_data bool

Whether to include guild member data

False
before Absent[Optional[Snowflake_Type]]

Snowflake of a user to get before

MISSING
after Absent[Optional[Snowflake_Type]]

Snowflake of a user to get after

MISSING

Note

This method is paginated

Source code in interactions/models/discord/scheduled_event.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
async def fetch_event_users(
    self,
    limit: Optional[int] = 100,
    with_member_data: bool = False,
    before: Absent[Optional["Snowflake_Type"]] = MISSING,
    after: Absent[Optional["Snowflake_Type"]] = MISSING,
) -> List[Union["Member", "User"]]:
    """
    Fetch event users.

    Args:
        limit: Discord defualts to 100
        with_member_data: Whether to include guild member data
        before: Snowflake of a user to get before
        after: Snowflake of a user to get after

    !!! note
        This method is paginated

    """
    event_users = await self._client.http.get_scheduled_event_users(
        self._guild_id, self.id, limit, with_member_data, before, after
    )
    participants = []
    for u in event_users:
        if member := u.get("member"):
            u["member"]["user"] = u["user"]
            participants.append(self._client.cache.place_member_data(self._guild_id, member))
        else:
            participants.append(self._client.cache.place_user_data(u["user"]))

    return participants

get_channel()

Returns the channel this event is scheduled in if it is scheduled in a channel.

Source code in interactions/models/discord/scheduled_event.py
117
118
119
120
121
def get_channel(self) -> Optional[Union["GuildVoice", "GuildStageVoice"]]:
    """Returns the channel this event is scheduled in if it is scheduled in a channel."""
    if self._channel_id:
        return self._client.cache.get_channel(self._channel_id)
    return None

Sticker

Bases: StickerItem

Represents a sticker that can be sent in messages.

Source code in interactions/models/discord/sticker.py
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class Sticker(StickerItem):
    """Represents a sticker that can be sent in messages."""

    pack_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None, converter=optional(to_snowflake))
    """For standard stickers, id of the pack the sticker is from."""
    description: Optional[str] = attrs.field(repr=False, default=None)
    """Description of the sticker."""
    tags: str = attrs.field(repr=False)
    """autocomplete/suggestion tags for the sticker (max 200 characters)"""
    type: Union[StickerTypes, int] = attrs.field(repr=False, converter=StickerTypes)
    """Type of sticker."""
    available: Optional[bool] = attrs.field(repr=False, default=True)
    """Whether this guild sticker can be used, may be false due to loss of Server Boosts."""
    sort_value: Optional[int] = attrs.field(repr=False, default=None)
    """The standard sticker's sort order within its pack."""

    _user_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None, converter=optional(to_snowflake))
    _guild_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None, converter=optional(to_snowflake))

    async def fetch_creator(self, *, force: bool = False) -> "User":
        """
        Fetch the user who created this emoji.

        Args:
            force: Whether to force a fetch from the API

        Returns:
            User object

        """
        return await self._client.cache.fetch_user(self._user_id, force=force)

    def get_creator(self) -> "User":
        """
        Get the user who created this emoji.

        Returns:
            User object

        """
        return self._client.cache.get_user(self._user_id)

    async def fetch_guild(self, *, force: bool = False) -> "Guild":
        """
        Fetch the guild associated with this emoji.

        Args:
            force: Whether to force a fetch from the API

        Returns:
            Guild object

        """
        return await self._client.cache.fetch_guild(self._guild_id, force=force)

    def get_guild(self) -> "Guild":
        """
        Get the guild associated with this emoji.

        Returns:
            Guild object

        """
        return self._client.cache.get_guild(self._guild_id)

    async def edit(
        self,
        *,
        name: Absent[Optional[str]] = MISSING,
        description: Absent[Optional[str]] = MISSING,
        tags: Absent[Optional[str]] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "Sticker":
        """
        Edit a sticker.

        Args:
            name: New name of the sticker
            description: New description of the sticker
            tags: New tags of the sticker
            reason: Reason for the edit

        Returns:
            The updated sticker instance

        """
        if not self._guild_id:
            raise ValueError("You can only edit guild stickers.")

        payload = dict_filter_none({"name": name, "description": description, "tags": tags})
        sticker_data = await self._client.http.modify_guild_sticker(payload, self._guild_id, self.id, reason)
        return self.update_from_dict(sticker_data)

    async def delete(self, reason: Optional[str] = MISSING) -> None:
        """
        Delete a sticker.

        Args:
            reason: Reason for the deletion

        Raises:
            ValueError: If you attempt to delete a non-guild sticker

        """
        if not self._guild_id:
            raise ValueError("You can only delete guild stickers.")

        await self._client.http.delete_guild_sticker(self._guild_id, self.id, reason)

    @property
    def url(self) -> str:
        """CDN url for the sticker."""
        return f"https://media.discordapp.net/stickers/{self.id}.webp"

available: Optional[bool] = attrs.field(repr=False, default=True) class-attribute

Whether this guild sticker can be used, may be false due to loss of Server Boosts.

description: Optional[str] = attrs.field(repr=False, default=None) class-attribute

Description of the sticker.

pack_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None, converter=optional(to_snowflake)) class-attribute

For standard stickers, id of the pack the sticker is from.

sort_value: Optional[int] = attrs.field(repr=False, default=None) class-attribute

The standard sticker's sort order within its pack.

tags: str = attrs.field(repr=False) class-attribute

autocomplete/suggestion tags for the sticker (max 200 characters)

type: Union[StickerTypes, int] = attrs.field(repr=False, converter=StickerTypes) class-attribute

Type of sticker.

url: str property

CDN url for the sticker.

delete(reason=MISSING) async

Delete a sticker.

Parameters:

Name Type Description Default
reason Optional[str]

Reason for the deletion

MISSING

Raises:

Type Description
ValueError

If you attempt to delete a non-guild sticker

Source code in interactions/models/discord/sticker.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
async def delete(self, reason: Optional[str] = MISSING) -> None:
    """
    Delete a sticker.

    Args:
        reason: Reason for the deletion

    Raises:
        ValueError: If you attempt to delete a non-guild sticker

    """
    if not self._guild_id:
        raise ValueError("You can only delete guild stickers.")

    await self._client.http.delete_guild_sticker(self._guild_id, self.id, reason)

edit(*, name=MISSING, description=MISSING, tags=MISSING, reason=MISSING) async

Edit a sticker.

Parameters:

Name Type Description Default
name Absent[Optional[str]]

New name of the sticker

MISSING
description Absent[Optional[str]]

New description of the sticker

MISSING
tags Absent[Optional[str]]

New tags of the sticker

MISSING
reason Absent[Optional[str]]

Reason for the edit

MISSING

Returns:

Type Description
Sticker

The updated sticker instance

Source code in interactions/models/discord/sticker.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
async def edit(
    self,
    *,
    name: Absent[Optional[str]] = MISSING,
    description: Absent[Optional[str]] = MISSING,
    tags: Absent[Optional[str]] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "Sticker":
    """
    Edit a sticker.

    Args:
        name: New name of the sticker
        description: New description of the sticker
        tags: New tags of the sticker
        reason: Reason for the edit

    Returns:
        The updated sticker instance

    """
    if not self._guild_id:
        raise ValueError("You can only edit guild stickers.")

    payload = dict_filter_none({"name": name, "description": description, "tags": tags})
    sticker_data = await self._client.http.modify_guild_sticker(payload, self._guild_id, self.id, reason)
    return self.update_from_dict(sticker_data)

fetch_creator(*, force=False) async

Fetch the user who created this emoji.

Parameters:

Name Type Description Default
force bool

Whether to force a fetch from the API

False

Returns:

Type Description
User

User object

Source code in interactions/models/discord/sticker.py
48
49
50
51
52
53
54
55
56
57
58
59
async def fetch_creator(self, *, force: bool = False) -> "User":
    """
    Fetch the user who created this emoji.

    Args:
        force: Whether to force a fetch from the API

    Returns:
        User object

    """
    return await self._client.cache.fetch_user(self._user_id, force=force)

fetch_guild(*, force=False) async

Fetch the guild associated with this emoji.

Parameters:

Name Type Description Default
force bool

Whether to force a fetch from the API

False

Returns:

Type Description
Guild

Guild object

Source code in interactions/models/discord/sticker.py
71
72
73
74
75
76
77
78
79
80
81
82
async def fetch_guild(self, *, force: bool = False) -> "Guild":
    """
    Fetch the guild associated with this emoji.

    Args:
        force: Whether to force a fetch from the API

    Returns:
        Guild object

    """
    return await self._client.cache.fetch_guild(self._guild_id, force=force)

get_creator()

Get the user who created this emoji.

Returns:

Type Description
User

User object

Source code in interactions/models/discord/sticker.py
61
62
63
64
65
66
67
68
69
def get_creator(self) -> "User":
    """
    Get the user who created this emoji.

    Returns:
        User object

    """
    return self._client.cache.get_user(self._user_id)

get_guild()

Get the guild associated with this emoji.

Returns:

Type Description
Guild

Guild object

Source code in interactions/models/discord/sticker.py
84
85
86
87
88
89
90
91
92
def get_guild(self) -> "Guild":
    """
    Get the guild associated with this emoji.

    Returns:
        Guild object

    """
    return self._client.cache.get_guild(self._guild_id)

StickerItem

Bases: DiscordObject

Source code in interactions/models/discord/sticker.py
20
21
22
23
24
25
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class StickerItem(DiscordObject):
    name: str = attrs.field(repr=True)
    """Name of the sticker."""
    format_type: StickerFormatType = attrs.field(repr=True, converter=StickerFormatType)
    """Type of sticker image format."""

format_type: StickerFormatType = attrs.field(repr=True, converter=StickerFormatType) class-attribute

Type of sticker image format.

name: str = attrs.field(repr=True) class-attribute

Name of the sticker.

StickerPack

Bases: DiscordObject

Represents a pack of standard stickers.

Source code in interactions/models/discord/sticker.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class StickerPack(DiscordObject):
    """Represents a pack of standard stickers."""

    stickers: List["Sticker"] = attrs.field(repr=False, factory=list)
    """The stickers in the pack."""
    name: str = attrs.field(repr=True)
    """Name of the sticker pack."""
    sku_id: "Snowflake_Type" = attrs.field(repr=True)
    """id of the pack's SKU."""
    cover_sticker_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None)
    """id of a sticker in the pack which is shown as the pack's icon."""
    description: str = attrs.field(repr=False)
    """Description of the sticker pack."""
    banner_asset_id: "Snowflake_Type" = attrs.field(repr=False)  # TODO CDN Asset
    """id of the sticker pack's banner image."""

banner_asset_id: Snowflake_Type = attrs.field(repr=False) class-attribute

id of the sticker pack's banner image.

cover_sticker_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

id of a sticker in the pack which is shown as the pack's icon.

description: str = attrs.field(repr=False) class-attribute

Description of the sticker pack.

name: str = attrs.field(repr=True) class-attribute

Name of the sticker pack.

sku_id: Snowflake_Type = attrs.field(repr=True) class-attribute

id of the pack's SKU.

stickers: List[Sticker] = attrs.field(repr=False, factory=list) class-attribute

The stickers in the pack.

Webhook

Bases: DiscordObject, SendMixin

Source code in interactions/models/discord/webhooks.py
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class Webhook(DiscordObject, SendMixin):
    type: WebhookTypes = attrs.field(
        repr=False,
    )
    """The type of webhook"""

    application_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None)
    """the bot/OAuth2 application that created this webhook"""

    guild_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None)
    """the guild id this webhook is for, if any"""
    channel_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None)
    """the channel id this webhook is for, if any"""
    user_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None)
    """the user this webhook was created by"""

    name: Optional[str] = attrs.field(repr=False, default=None)
    """the default name of the webhook"""
    avatar: Optional[str] = attrs.field(repr=False, default=None)
    """the default user avatar hash of the webhook"""
    token: str = attrs.field(repr=False, default=MISSING)
    """the secure token of the webhook (returned for Incoming Webhooks)"""
    url: Optional[str] = attrs.field(repr=False, default=None)
    """the url used for executing the webhook (returned by the webhooks OAuth2 flow)"""

    source_guild_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None)
    """the guild of the channel that this webhook is following (returned for Channel Follower Webhooks)"""
    source_channel_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None)
    """the channel that this webhook is following (returned for Channel Follower Webhooks)"""

    @classmethod
    def from_url(cls, url: str, client: "Client") -> "Webhook":
        """
        Webhook object from a URL.

        Args:
            client: The client to use to make the request.
            url: Webhook URL

        Returns:
            A Webhook object.

        """
        match = re.search(r"discord(?:app)?\.com/api/webhooks/(?P<id>[0-9]{17,})/(?P<token>[\w\-.]{60,68})", url)
        if match is None:
            raise ValueError("Invalid webhook URL given.")

        data: Dict[str, Any] = match.groupdict()
        data["type"] = WebhookTypes.INCOMING
        return cls.from_dict(data, client)

    @classmethod
    async def create(
        cls,
        client: "Client",
        channel: Union["Snowflake_Type", "TYPE_MESSAGEABLE_CHANNEL"],
        name: str,
        avatar: Absent["UPLOADABLE_TYPE"] = MISSING,
    ) -> "Webhook":
        """
        Create a webhook.

        Args:
            client: The bot's client
            channel: The channel to create the webhook in
            name: The name of the webhook
            avatar: An optional default avatar to use

        Returns:
            New webhook object

        Raises:
            ValueError: If you try to name the webhook "Clyde"

        """
        if name.lower() == "clyde":
            raise ValueError('Webhook names cannot be "Clyde"')

        if not isinstance(channel, (str, int)):
            channel = to_snowflake(channel)

        if avatar:
            avatar = to_image_data(avatar)

        data = await client.http.create_webhook(channel, name, avatar)

        return cls.from_dict(data, client)

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if data.get("user"):
            user = client.cache.place_user_data(data.pop("user"))
            data["user_id"] = user.id
        return data

    async def edit(
        self,
        *,
        name: Absent[str] = MISSING,
        avatar: Absent["UPLOADABLE_TYPE"] = MISSING,
        channel_id: Absent["Snowflake_Type"] = MISSING,
    ) -> None:
        """
        Edit this webhook.

        Args:
            name: The default name of the webhook.
            avatar: The image for the default webhook avatar.
            channel_id: The new channel id this webhook should be moved to.

        Raises:
            ValueError: If you try to name the webhook "Clyde"

        """
        if name.lower() == "clyde":
            raise ValueError('Webhook names cannot be "Clyde"')

        data = await self._client.http.modify_webhook(
            self.id, name, to_image_data(avatar), to_optional_snowflake(channel_id), self.token
        )
        self.update_from_dict(data)

    async def delete(self) -> None:
        """Delete this webhook."""
        await self._client.http.delete_webhook(self.id, self.token)

    async def send(
        self,
        content: Optional[str] = None,
        *,
        embed: Optional[Union["Embed", dict]] = None,
        embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
        components: Optional[
            Union[
                List[List[Union["BaseComponent", dict]]],
                List[Union["BaseComponent", dict]],
                "BaseComponent",
                dict,
            ]
        ] = None,
        stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
        allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
        reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
        files: Optional[Union["UPLOADABLE_TYPE", List["UPLOADABLE_TYPE"]]] = None,
        file: Optional["UPLOADABLE_TYPE"] = None,
        tts: bool = False,
        suppress_embeds: bool = False,
        flags: Optional[Union[int, "MessageFlags"]] = None,
        poll: "Optional[Poll | dict]" = None,
        username: str | None = None,
        avatar_url: str | None = None,
        wait: bool = False,
        thread: "Snowflake_Type" = None,
        **kwargs,
    ) -> Optional["Message"]:
        """
        Send a message as this webhook.

        Args:
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            stickers: IDs of up to 3 stickers in the server to send in the message.
            allowed_mentions: Allowed mentions for the message.
            reply_to: Message to reference, must be from the same channel.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.
            suppress_embeds: Should embeds be suppressed on this send
            flags: Message flags to apply.
            poll: A poll.
            username: The username to use
            avatar_url: The url of an image to use as the avatar
            wait: Waits for confirmation of delivery. Set this to True if you intend to edit the message
            thread: Send this webhook to a thread channel

        Returns:
            New message object that was sent if `wait` is set to True

        """
        if not self.token:
            raise ForeignWebhookException("You cannot send messages with a webhook without a token!")

        if not content and not embeds and not embed and not files and not file and not stickers:
            raise EmptyMessageException("You cannot send a message without any content, embeds, files, or stickers")

        if suppress_embeds:
            if isinstance(flags, int):
                flags = MessageFlags(flags)
            flags = flags | MessageFlags.SUPPRESS_EMBEDS

        message_payload = process_message_payload(
            content=content,
            embeds=embeds or embed,
            components=components,
            stickers=stickers,
            allowed_mentions=allowed_mentions,
            reply_to=reply_to,
            tts=tts,
            flags=flags,
            poll=poll,
            username=username,
            avatar_url=avatar_url,
            **kwargs,
        )

        message_data = await self._client.http.execute_webhook(
            self.id,
            self.token,
            message_payload,
            wait,
            to_optional_snowflake(thread),
            files=files or file,
        )
        if message_data:
            return self._client.cache.place_message_data(message_data)

    async def fetch_message(self, message_id: Union["Message", "Snowflake_Type"]) -> Optional["Message"]:
        """
        Returns a previously-sent webhook message from the same token. Returns a message object on success.

        Args:
            message_id: ID of message to retrieve.

        Returns:
            The message object fetched. If the message is not found, returns None.

        """
        message_id = to_snowflake(message_id)
        try:
            msg_data = await self._client.http.get_webhook_message(self.id, self.token, message_id)
        except NotFound:
            return None
        return self._client.cache.place_message_data(msg_data)

    async def edit_message(
        self,
        message: Union["Message", "Snowflake_Type"],
        *,
        content: Optional[str] = None,
        embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
        components: Optional[
            Union[
                List[List[Union["BaseComponent", dict]]],
                List[Union["BaseComponent", dict]],
                "BaseComponent",
                dict,
            ]
        ] = None,
        stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
        allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
        reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
        files: Optional[Union["UPLOADABLE_TYPE", List["UPLOADABLE_TYPE"]]] = None,
        file: Optional["UPLOADABLE_TYPE"] = None,
        tts: bool = False,
        flags: Optional[Union[int, "MessageFlags"]] = None,
    ) -> Optional["Message"]:
        """
        Edit a message as this webhook.

        Args:
            message: Message to edit
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            stickers: IDs of up to 3 stickers in the server to send in the message.
            allowed_mentions: Allowed mentions for the message.
            reply_to: Message to reference, must be from the same channel.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.
            flags: Message flags to apply.

        Returns:
            Updated message object that was sent if `wait` is set to True

        """
        message_payload = process_message_payload(
            content=content,
            embeds=embeds,
            components=components,
            stickers=stickers,
            allowed_mentions=allowed_mentions,
            reply_to=reply_to,
            tts=tts,
            flags=flags,
        )
        msg_data = await self._client.http.edit_webhook_message(
            self.id, self.token, to_snowflake(message), message_payload, files=files or file
        )
        if msg_data:
            return self._client.cache.place_message_data(msg_data)

    async def delete_message(
        self,
        message: Union["Message", "Snowflake_Type"],
        *,
        delay: int = 0,
    ) -> None:
        """
        Delete a message as this webhook.

        Args:
            message: Message to delete
            delay: Seconds to wait before deleting message.

        """

        async def _delete() -> None:
            if delay:
                await asyncio.sleep(delay)

            await self._client.http.delete_webhook_message(self.id, self.token, to_snowflake(message))

        if delay:
            return asyncio.create_task(_delete())

        return await _delete()

application_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

the bot/OAuth2 application that created this webhook

avatar: Optional[str] = attrs.field(repr=False, default=None) class-attribute

the default user avatar hash of the webhook

channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

the channel id this webhook is for, if any

guild_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

the guild id this webhook is for, if any

name: Optional[str] = attrs.field(repr=False, default=None) class-attribute

the default name of the webhook

source_channel_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

the channel that this webhook is following (returned for Channel Follower Webhooks)

source_guild_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

the guild of the channel that this webhook is following (returned for Channel Follower Webhooks)

token: str = attrs.field(repr=False, default=MISSING) class-attribute

the secure token of the webhook (returned for Incoming Webhooks)

type: WebhookTypes = attrs.field(repr=False) class-attribute

The type of webhook

url: Optional[str] = attrs.field(repr=False, default=None) class-attribute

the url used for executing the webhook (returned by the webhooks OAuth2 flow)

user_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

the user this webhook was created by

create(client, channel, name, avatar=MISSING) classmethod async

Create a webhook.

Parameters:

Name Type Description Default
client Client

The bot's client

required
channel Union[Snowflake_Type, TYPE_MESSAGEABLE_CHANNEL]

The channel to create the webhook in

required
name str

The name of the webhook

required
avatar Absent[UPLOADABLE_TYPE]

An optional default avatar to use

MISSING

Returns:

Type Description
Webhook

New webhook object

Raises:

Type Description
ValueError

If you try to name the webhook "Clyde"

Source code in interactions/models/discord/webhooks.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
@classmethod
async def create(
    cls,
    client: "Client",
    channel: Union["Snowflake_Type", "TYPE_MESSAGEABLE_CHANNEL"],
    name: str,
    avatar: Absent["UPLOADABLE_TYPE"] = MISSING,
) -> "Webhook":
    """
    Create a webhook.

    Args:
        client: The bot's client
        channel: The channel to create the webhook in
        name: The name of the webhook
        avatar: An optional default avatar to use

    Returns:
        New webhook object

    Raises:
        ValueError: If you try to name the webhook "Clyde"

    """
    if name.lower() == "clyde":
        raise ValueError('Webhook names cannot be "Clyde"')

    if not isinstance(channel, (str, int)):
        channel = to_snowflake(channel)

    if avatar:
        avatar = to_image_data(avatar)

    data = await client.http.create_webhook(channel, name, avatar)

    return cls.from_dict(data, client)

delete() async

Delete this webhook.

Source code in interactions/models/discord/webhooks.py
168
169
170
async def delete(self) -> None:
    """Delete this webhook."""
    await self._client.http.delete_webhook(self.id, self.token)

delete_message(message, *, delay=0) async

Delete a message as this webhook.

Parameters:

Name Type Description Default
message Union[Message, Snowflake_Type]

Message to delete

required
delay int

Seconds to wait before deleting message.

0
Source code in interactions/models/discord/webhooks.py
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
async def delete_message(
    self,
    message: Union["Message", "Snowflake_Type"],
    *,
    delay: int = 0,
) -> None:
    """
    Delete a message as this webhook.

    Args:
        message: Message to delete
        delay: Seconds to wait before deleting message.

    """

    async def _delete() -> None:
        if delay:
            await asyncio.sleep(delay)

        await self._client.http.delete_webhook_message(self.id, self.token, to_snowflake(message))

    if delay:
        return asyncio.create_task(_delete())

    return await _delete()

edit(*, name=MISSING, avatar=MISSING, channel_id=MISSING) async

Edit this webhook.

Parameters:

Name Type Description Default
name Absent[str]

The default name of the webhook.

MISSING
avatar Absent[UPLOADABLE_TYPE]

The image for the default webhook avatar.

MISSING
channel_id Absent[Snowflake_Type]

The new channel id this webhook should be moved to.

MISSING

Raises:

Type Description
ValueError

If you try to name the webhook "Clyde"

Source code in interactions/models/discord/webhooks.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
async def edit(
    self,
    *,
    name: Absent[str] = MISSING,
    avatar: Absent["UPLOADABLE_TYPE"] = MISSING,
    channel_id: Absent["Snowflake_Type"] = MISSING,
) -> None:
    """
    Edit this webhook.

    Args:
        name: The default name of the webhook.
        avatar: The image for the default webhook avatar.
        channel_id: The new channel id this webhook should be moved to.

    Raises:
        ValueError: If you try to name the webhook "Clyde"

    """
    if name.lower() == "clyde":
        raise ValueError('Webhook names cannot be "Clyde"')

    data = await self._client.http.modify_webhook(
        self.id, name, to_image_data(avatar), to_optional_snowflake(channel_id), self.token
    )
    self.update_from_dict(data)

edit_message(message, *, content=None, embeds=None, components=None, stickers=None, allowed_mentions=None, reply_to=None, files=None, file=None, tts=False, flags=None) async

Edit a message as this webhook.

Parameters:

Name Type Description Default
message Union[Message, Snowflake_Type]

Message to edit

required
content Optional[str]

Message text content.

None
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
stickers Optional[Union[List[Union[Sticker, Snowflake_Type]], Sticker, Snowflake_Type]]

IDs of up to 3 stickers in the server to send in the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
reply_to Optional[Union[MessageReference, Message, dict, Snowflake_Type]]

Message to reference, must be from the same channel.

None
files Optional[Union[UPLOADABLE_TYPE, List[UPLOADABLE_TYPE]]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
file Optional[UPLOADABLE_TYPE]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False
flags Optional[Union[int, MessageFlags]]

Message flags to apply.

None

Returns:

Type Description
Optional[Message]

Updated message object that was sent if wait is set to True

Source code in interactions/models/discord/webhooks.py
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
async def edit_message(
    self,
    message: Union["Message", "Snowflake_Type"],
    *,
    content: Optional[str] = None,
    embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
    components: Optional[
        Union[
            List[List[Union["BaseComponent", dict]]],
            List[Union["BaseComponent", dict]],
            "BaseComponent",
            dict,
        ]
    ] = None,
    stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
    allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
    reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
    files: Optional[Union["UPLOADABLE_TYPE", List["UPLOADABLE_TYPE"]]] = None,
    file: Optional["UPLOADABLE_TYPE"] = None,
    tts: bool = False,
    flags: Optional[Union[int, "MessageFlags"]] = None,
) -> Optional["Message"]:
    """
    Edit a message as this webhook.

    Args:
        message: Message to edit
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        stickers: IDs of up to 3 stickers in the server to send in the message.
        allowed_mentions: Allowed mentions for the message.
        reply_to: Message to reference, must be from the same channel.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.
        flags: Message flags to apply.

    Returns:
        Updated message object that was sent if `wait` is set to True

    """
    message_payload = process_message_payload(
        content=content,
        embeds=embeds,
        components=components,
        stickers=stickers,
        allowed_mentions=allowed_mentions,
        reply_to=reply_to,
        tts=tts,
        flags=flags,
    )
    msg_data = await self._client.http.edit_webhook_message(
        self.id, self.token, to_snowflake(message), message_payload, files=files or file
    )
    if msg_data:
        return self._client.cache.place_message_data(msg_data)

fetch_message(message_id) async

Returns a previously-sent webhook message from the same token. Returns a message object on success.

Parameters:

Name Type Description Default
message_id Union[Message, Snowflake_Type]

ID of message to retrieve.

required

Returns:

Type Description
Optional[Message]

The message object fetched. If the message is not found, returns None.

Source code in interactions/models/discord/webhooks.py
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
async def fetch_message(self, message_id: Union["Message", "Snowflake_Type"]) -> Optional["Message"]:
    """
    Returns a previously-sent webhook message from the same token. Returns a message object on success.

    Args:
        message_id: ID of message to retrieve.

    Returns:
        The message object fetched. If the message is not found, returns None.

    """
    message_id = to_snowflake(message_id)
    try:
        msg_data = await self._client.http.get_webhook_message(self.id, self.token, message_id)
    except NotFound:
        return None
    return self._client.cache.place_message_data(msg_data)

from_url(url, client) classmethod

Webhook object from a URL.

Parameters:

Name Type Description Default
client Client

The client to use to make the request.

required
url str

Webhook URL

required

Returns:

Type Description
Webhook

A Webhook object.

Source code in interactions/models/discord/webhooks.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
@classmethod
def from_url(cls, url: str, client: "Client") -> "Webhook":
    """
    Webhook object from a URL.

    Args:
        client: The client to use to make the request.
        url: Webhook URL

    Returns:
        A Webhook object.

    """
    match = re.search(r"discord(?:app)?\.com/api/webhooks/(?P<id>[0-9]{17,})/(?P<token>[\w\-.]{60,68})", url)
    if match is None:
        raise ValueError("Invalid webhook URL given.")

    data: Dict[str, Any] = match.groupdict()
    data["type"] = WebhookTypes.INCOMING
    return cls.from_dict(data, client)

send(content=None, *, embed=None, embeds=None, components=None, stickers=None, allowed_mentions=None, reply_to=None, files=None, file=None, tts=False, suppress_embeds=False, flags=None, poll=None, username=None, avatar_url=None, wait=False, thread=None, **kwargs) async

Send a message as this webhook.

Parameters:

Name Type Description Default
content Optional[str]

Message text content.

None
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed Optional[Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
stickers Optional[Union[List[Union[Sticker, Snowflake_Type]], Sticker, Snowflake_Type]]

IDs of up to 3 stickers in the server to send in the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
reply_to Optional[Union[MessageReference, Message, dict, Snowflake_Type]]

Message to reference, must be from the same channel.

None
files Optional[Union[UPLOADABLE_TYPE, List[UPLOADABLE_TYPE]]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
file Optional[UPLOADABLE_TYPE]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False
suppress_embeds bool

Should embeds be suppressed on this send

False
flags Optional[Union[int, MessageFlags]]

Message flags to apply.

None
poll Optional[Poll | dict]

A poll.

None
username str | None

The username to use

None
avatar_url str | None

The url of an image to use as the avatar

None
wait bool

Waits for confirmation of delivery. Set this to True if you intend to edit the message

False
thread Snowflake_Type

Send this webhook to a thread channel

None

Returns:

Type Description
Optional[Message]

New message object that was sent if wait is set to True

Source code in interactions/models/discord/webhooks.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
async def send(
    self,
    content: Optional[str] = None,
    *,
    embed: Optional[Union["Embed", dict]] = None,
    embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
    components: Optional[
        Union[
            List[List[Union["BaseComponent", dict]]],
            List[Union["BaseComponent", dict]],
            "BaseComponent",
            dict,
        ]
    ] = None,
    stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
    allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
    reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
    files: Optional[Union["UPLOADABLE_TYPE", List["UPLOADABLE_TYPE"]]] = None,
    file: Optional["UPLOADABLE_TYPE"] = None,
    tts: bool = False,
    suppress_embeds: bool = False,
    flags: Optional[Union[int, "MessageFlags"]] = None,
    poll: "Optional[Poll | dict]" = None,
    username: str | None = None,
    avatar_url: str | None = None,
    wait: bool = False,
    thread: "Snowflake_Type" = None,
    **kwargs,
) -> Optional["Message"]:
    """
    Send a message as this webhook.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        stickers: IDs of up to 3 stickers in the server to send in the message.
        allowed_mentions: Allowed mentions for the message.
        reply_to: Message to reference, must be from the same channel.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.
        suppress_embeds: Should embeds be suppressed on this send
        flags: Message flags to apply.
        poll: A poll.
        username: The username to use
        avatar_url: The url of an image to use as the avatar
        wait: Waits for confirmation of delivery. Set this to True if you intend to edit the message
        thread: Send this webhook to a thread channel

    Returns:
        New message object that was sent if `wait` is set to True

    """
    if not self.token:
        raise ForeignWebhookException("You cannot send messages with a webhook without a token!")

    if not content and not embeds and not embed and not files and not file and not stickers:
        raise EmptyMessageException("You cannot send a message without any content, embeds, files, or stickers")

    if suppress_embeds:
        if isinstance(flags, int):
            flags = MessageFlags(flags)
        flags = flags | MessageFlags.SUPPRESS_EMBEDS

    message_payload = process_message_payload(
        content=content,
        embeds=embeds or embed,
        components=components,
        stickers=stickers,
        allowed_mentions=allowed_mentions,
        reply_to=reply_to,
        tts=tts,
        flags=flags,
        poll=poll,
        username=username,
        avatar_url=avatar_url,
        **kwargs,
    )

    message_data = await self._client.http.execute_webhook(
        self.id,
        self.token,
        message_payload,
        wait,
        to_optional_snowflake(thread),
        files=files or file,
    )
    if message_data:
        return self._client.cache.place_message_data(message_data)

WebhookTypes

Bases: IntEnum

Source code in interactions/models/discord/webhooks.py
36
37
38
39
40
41
42
class WebhookTypes(IntEnum):
    INCOMING = 1
    """Incoming Webhooks can post messages to channels with a generated token"""
    CHANNEL_FOLLOWER = 2
    """Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages into channels"""
    APPLICATION = 3
    """Application webhooks are webhooks used with Interactions"""

APPLICATION = 3 class-attribute

Application webhooks are webhooks used with Interactions

CHANNEL_FOLLOWER = 2 class-attribute

Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages into channels

INCOMING = 1 class-attribute

Incoming Webhooks can post messages to channels with a generated token

BaseChannel

Bases: DiscordObject

Source code in interactions/models/discord/channel.py
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
@attrs.define(eq=False, order=False, hash=False, slots=False, kw_only=True)
class BaseChannel(DiscordObject):
    name: Optional[str] = attrs.field(repr=True, default=None)
    """The name of the channel (1-100 characters)"""
    type: Union[ChannelType, int] = attrs.field(repr=True, converter=ChannelType)
    """The channel topic (0-1024 characters)"""
    permissions: Optional[Permissions] = attrs.field(repr=False, default=None, converter=optional_c(Permissions))
    """Calculated permissions for the bot in this channel, only given when using channels as an option with slash commands"""

    @classmethod
    def from_dict_factory(cls, data: dict, client: "Client") -> "TYPE_ALL_CHANNEL":
        """
        Creates a channel object of the appropriate type.

        Args:
            data: The channel data.
            client: The bot.

        Returns:
            The new channel object.

        """
        channel_type = data.get("type")
        channel_class = TYPE_CHANNEL_MAPPING.get(channel_type, None)
        if not channel_class:
            client.logger.error(f"Unsupported channel type for {data} ({channel_type}).")
            channel_class = BaseChannel

        if channel_class == GuildPublicThread:
            # attempt to determine if this thread is a forum post (thanks discord)
            parent_channel = client.cache.get_channel(data["parent_id"])
            if parent_channel and parent_channel.type == ChannelType.GUILD_FORUM:
                channel_class = GuildForumPost

        return channel_class.from_dict(data, client)

    @property
    def mention(self) -> str:
        """Returns a string that would mention the channel."""
        return f"<#{self.id}>"

    async def edit(
        self,
        *,
        name: Absent[str] = MISSING,
        icon: Absent[UPLOADABLE_TYPE] = MISSING,
        type: Absent[ChannelType] = MISSING,
        position: Absent[int] = MISSING,
        topic: Absent[str] = MISSING,
        nsfw: Absent[bool] = MISSING,
        rate_limit_per_user: Absent[int] = MISSING,
        bitrate: Absent[int] = MISSING,
        user_limit: Absent[int] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        parent_id: Absent[Snowflake_Type] = MISSING,
        rtc_region: Absent[Union["models.VoiceRegion", str]] = MISSING,
        video_quality_mode: Absent[VideoQualityMode] = MISSING,
        default_auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
        flags: Absent[Union[int, ChannelFlags]] = MISSING,
        archived: Absent[bool] = MISSING,
        auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
        locked: Absent[bool] = MISSING,
        invitable: Absent[bool] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "TYPE_ALL_CHANNEL":
        """
        Edits the channel.

        Args:
            name: 1-100 character channel name
            icon: DM Group icon
            type: The type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature
            position: The position of the channel in the left-hand listing
            topic: 0-1024 character channel topic
            nsfw: Whether the channel is nsfw
            rate_limit_per_user: Amount of seconds a user has to wait before sending another message (0-21600)
            bitrate: The bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)
            user_limit: The user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit
            permission_overwrites: Channel or category-specific permissions
            parent_id: The id of the new parent category for a channel
            rtc_region: Channel voice region id, automatic when set to None.
            video_quality_mode: The camera video quality mode of the voice channel
            default_auto_archive_duration: The default duration that the clients use (not the API) for newly created threads in the channel, in minutes, to automatically archive the thread after recent activity
            flags: Channel flags combined as a bitfield. Only REQUIRE_TAG is supported for now.
            archived: Whether the thread is archived
            auto_archive_duration: Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
            locked: Whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
            invitable: Whether non-moderators can add other non-moderators to a thread; only available on private threads
            reason: The reason for editing the channel

        Returns:
            The edited channel. May be a new object if the channel type changes.

        """
        payload = {
            "name": name,
            "icon": to_image_data(icon),
            "type": type,
            "position": position,
            "topic": topic,
            "nsfw": nsfw,
            "rate_limit_per_user": rate_limit_per_user,
            "bitrate": bitrate,
            "user_limit": user_limit,
            "permission_overwrites": process_permission_overwrites(permission_overwrites),
            "parent_id": to_optional_snowflake(parent_id),
            "rtc_region": rtc_region.id if isinstance(rtc_region, models.VoiceRegion) else rtc_region,
            "video_quality_mode": video_quality_mode,
            "default_auto_archive_duration": default_auto_archive_duration,
            "flags": flags,
            "archived": archived,
            "auto_archive_duration": auto_archive_duration,
            "locked": locked,
            "invitable": invitable,
            **kwargs,
        }
        channel_data = await self._client.http.modify_channel(self.id, payload, reason)
        if not channel_data:
            raise TooManyChanges(
                "You have changed this channel too frequently, you need to wait a while before trying again."
            ) from None

        return self._client.cache.place_channel_data(channel_data)

    async def delete(self, reason: Absent[Optional[str]] = MISSING) -> None:
        """
        Delete this channel.

        Args:
            reason: The reason for deleting this channel

        """
        await self._client.http.delete_channel(self.id, reason)

mention: str property

Returns a string that would mention the channel.

name: Optional[str] = attrs.field(repr=True, default=None) class-attribute

The name of the channel (1-100 characters)

permissions: Optional[Permissions] = attrs.field(repr=False, default=None, converter=optional_c(Permissions)) class-attribute

Calculated permissions for the bot in this channel, only given when using channels as an option with slash commands

type: Union[ChannelType, int] = attrs.field(repr=True, converter=ChannelType) class-attribute

The channel topic (0-1024 characters)

delete(reason=MISSING) async

Delete this channel.

Parameters:

Name Type Description Default
reason Absent[Optional[str]]

The reason for deleting this channel

MISSING
Source code in interactions/models/discord/channel.py
903
904
905
906
907
908
909
910
911
async def delete(self, reason: Absent[Optional[str]] = MISSING) -> None:
    """
    Delete this channel.

    Args:
        reason: The reason for deleting this channel

    """
    await self._client.http.delete_channel(self.id, reason)

edit(*, name=MISSING, icon=MISSING, type=MISSING, position=MISSING, topic=MISSING, nsfw=MISSING, rate_limit_per_user=MISSING, bitrate=MISSING, user_limit=MISSING, permission_overwrites=MISSING, parent_id=MISSING, rtc_region=MISSING, video_quality_mode=MISSING, default_auto_archive_duration=MISSING, flags=MISSING, archived=MISSING, auto_archive_duration=MISSING, locked=MISSING, invitable=MISSING, reason=MISSING, **kwargs) async

Edits the channel.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
icon Absent[UPLOADABLE_TYPE]

DM Group icon

MISSING
type Absent[ChannelType]

The type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature

MISSING
position Absent[int]

The position of the channel in the left-hand listing

MISSING
topic Absent[str]

0-1024 character channel topic

MISSING
nsfw Absent[bool]

Whether the channel is nsfw

MISSING
rate_limit_per_user Absent[int]

Amount of seconds a user has to wait before sending another message (0-21600)

MISSING
bitrate Absent[int]

The bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)

MISSING
user_limit Absent[int]

The user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Channel or category-specific permissions

MISSING
parent_id Absent[Snowflake_Type]

The id of the new parent category for a channel

MISSING
rtc_region Absent[Union[VoiceRegion, str]]

Channel voice region id, automatic when set to None.

MISSING
video_quality_mode Absent[VideoQualityMode]

The camera video quality mode of the voice channel

MISSING
default_auto_archive_duration Absent[AutoArchiveDuration]

The default duration that the clients use (not the API) for newly created threads in the channel, in minutes, to automatically archive the thread after recent activity

MISSING
flags Absent[Union[int, ChannelFlags]]

Channel flags combined as a bitfield. Only REQUIRE_TAG is supported for now.

MISSING
archived Absent[bool]

Whether the thread is archived

MISSING
auto_archive_duration Absent[AutoArchiveDuration]

Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

MISSING
locked Absent[bool]

Whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it

MISSING
invitable Absent[bool]

Whether non-moderators can add other non-moderators to a thread; only available on private threads

MISSING
reason Absent[str]

The reason for editing the channel

MISSING

Returns:

Type Description
TYPE_ALL_CHANNEL

The edited channel. May be a new object if the channel type changes.

Source code in interactions/models/discord/channel.py
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
async def edit(
    self,
    *,
    name: Absent[str] = MISSING,
    icon: Absent[UPLOADABLE_TYPE] = MISSING,
    type: Absent[ChannelType] = MISSING,
    position: Absent[int] = MISSING,
    topic: Absent[str] = MISSING,
    nsfw: Absent[bool] = MISSING,
    rate_limit_per_user: Absent[int] = MISSING,
    bitrate: Absent[int] = MISSING,
    user_limit: Absent[int] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    parent_id: Absent[Snowflake_Type] = MISSING,
    rtc_region: Absent[Union["models.VoiceRegion", str]] = MISSING,
    video_quality_mode: Absent[VideoQualityMode] = MISSING,
    default_auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
    flags: Absent[Union[int, ChannelFlags]] = MISSING,
    archived: Absent[bool] = MISSING,
    auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
    locked: Absent[bool] = MISSING,
    invitable: Absent[bool] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "TYPE_ALL_CHANNEL":
    """
    Edits the channel.

    Args:
        name: 1-100 character channel name
        icon: DM Group icon
        type: The type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature
        position: The position of the channel in the left-hand listing
        topic: 0-1024 character channel topic
        nsfw: Whether the channel is nsfw
        rate_limit_per_user: Amount of seconds a user has to wait before sending another message (0-21600)
        bitrate: The bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)
        user_limit: The user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit
        permission_overwrites: Channel or category-specific permissions
        parent_id: The id of the new parent category for a channel
        rtc_region: Channel voice region id, automatic when set to None.
        video_quality_mode: The camera video quality mode of the voice channel
        default_auto_archive_duration: The default duration that the clients use (not the API) for newly created threads in the channel, in minutes, to automatically archive the thread after recent activity
        flags: Channel flags combined as a bitfield. Only REQUIRE_TAG is supported for now.
        archived: Whether the thread is archived
        auto_archive_duration: Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
        locked: Whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
        invitable: Whether non-moderators can add other non-moderators to a thread; only available on private threads
        reason: The reason for editing the channel

    Returns:
        The edited channel. May be a new object if the channel type changes.

    """
    payload = {
        "name": name,
        "icon": to_image_data(icon),
        "type": type,
        "position": position,
        "topic": topic,
        "nsfw": nsfw,
        "rate_limit_per_user": rate_limit_per_user,
        "bitrate": bitrate,
        "user_limit": user_limit,
        "permission_overwrites": process_permission_overwrites(permission_overwrites),
        "parent_id": to_optional_snowflake(parent_id),
        "rtc_region": rtc_region.id if isinstance(rtc_region, models.VoiceRegion) else rtc_region,
        "video_quality_mode": video_quality_mode,
        "default_auto_archive_duration": default_auto_archive_duration,
        "flags": flags,
        "archived": archived,
        "auto_archive_duration": auto_archive_duration,
        "locked": locked,
        "invitable": invitable,
        **kwargs,
    }
    channel_data = await self._client.http.modify_channel(self.id, payload, reason)
    if not channel_data:
        raise TooManyChanges(
            "You have changed this channel too frequently, you need to wait a while before trying again."
        ) from None

    return self._client.cache.place_channel_data(channel_data)

from_dict_factory(data, client) classmethod

Creates a channel object of the appropriate type.

Parameters:

Name Type Description Default
data dict

The channel data.

required
client Client

The bot.

required

Returns:

Type Description
TYPE_ALL_CHANNEL

The new channel object.

Source code in interactions/models/discord/channel.py
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
@classmethod
def from_dict_factory(cls, data: dict, client: "Client") -> "TYPE_ALL_CHANNEL":
    """
    Creates a channel object of the appropriate type.

    Args:
        data: The channel data.
        client: The bot.

    Returns:
        The new channel object.

    """
    channel_type = data.get("type")
    channel_class = TYPE_CHANNEL_MAPPING.get(channel_type, None)
    if not channel_class:
        client.logger.error(f"Unsupported channel type for {data} ({channel_type}).")
        channel_class = BaseChannel

    if channel_class == GuildPublicThread:
        # attempt to determine if this thread is a forum post (thanks discord)
        parent_channel = client.cache.get_channel(data["parent_id"])
        if parent_channel and parent_channel.type == ChannelType.GUILD_FORUM:
            channel_class = GuildForumPost

    return channel_class.from_dict(data, client)

ChannelHistory

Bases: AsyncIterator

An async iterator for searching through a channel's history.

Attributes:

Name Type Description
channel BaseChannel

The channel to search through

limit BaseChannel

The maximum number of messages to return (set to 0 for no limit)

before Snowflake_Type

get messages before this message ID

after Snowflake_Type

get messages after this message ID

around Snowflake_Type

get messages "around" this message ID

Source code in interactions/models/discord/channel.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
class ChannelHistory(AsyncIterator):
    """
    An async iterator for searching through a channel's history.

    Attributes:
        channel: The channel to search through
        limit: The maximum number of messages to return (set to 0 for no limit)
        before: get messages before this message ID
        after: get messages after this message ID
        around: get messages "around" this message ID

    """

    def __init__(self, channel: "BaseChannel", limit=50, before=None, after=None, around=None) -> None:
        self.channel: "BaseChannel" = channel
        self.before: Snowflake_Type = before
        self.after: Snowflake_Type = after
        self.around: Snowflake_Type = around
        super().__init__(limit)

    async def fetch(self) -> List["models.Message"]:
        """
        Fetch additional objects.

        Your implementation of this method *must* return a list of objects.
        If no more objects are available, raise QueueEmpty

        Returns:
            List of objects

        Raises:
              QueueEmpty: when no more objects are available.

        """
        if self.after:
            if not self.last:
                self.last = namedtuple("temp", "id")
                self.last.id = self.after
            messages = await self.channel.fetch_messages(limit=self.get_limit, after=self.last.id)
            messages.sort(key=lambda x: x.id)

        elif self.around:
            messages = await self.channel.fetch_messages(limit=self.get_limit, around=self.around)
            # todo: decide how getting *more* messages from `around` would work
            self._limit = 1  # stops history from getting more messages

        else:
            if self.before and not self.last:
                self.last = namedtuple("temp", "id")
                self.last.id = self.before

            messages = await self.channel.fetch_messages(limit=self.get_limit, before=self.last.id)
            messages.sort(key=lambda x: x.id, reverse=True)
        return messages

fetch() async

Fetch additional objects.

Your implementation of this method must return a list of objects. If no more objects are available, raise QueueEmpty

Returns:

Type Description
List[Message]

List of objects

Raises:

Type Description
QueueEmpty

when no more objects are available.

Source code in interactions/models/discord/channel.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
async def fetch(self) -> List["models.Message"]:
    """
    Fetch additional objects.

    Your implementation of this method *must* return a list of objects.
    If no more objects are available, raise QueueEmpty

    Returns:
        List of objects

    Raises:
          QueueEmpty: when no more objects are available.

    """
    if self.after:
        if not self.last:
            self.last = namedtuple("temp", "id")
            self.last.id = self.after
        messages = await self.channel.fetch_messages(limit=self.get_limit, after=self.last.id)
        messages.sort(key=lambda x: x.id)

    elif self.around:
        messages = await self.channel.fetch_messages(limit=self.get_limit, around=self.around)
        # todo: decide how getting *more* messages from `around` would work
        self._limit = 1  # stops history from getting more messages

    else:
        if self.before and not self.last:
            self.last = namedtuple("temp", "id")
            self.last.id = self.before

        messages = await self.channel.fetch_messages(limit=self.get_limit, before=self.last.id)
        messages.sort(key=lambda x: x.id, reverse=True)
    return messages

DM

Bases: DMChannel

Source code in interactions/models/discord/channel.py
939
940
941
942
943
944
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class DM(DMChannel):
    @property
    def recipient(self) -> "models.User":
        """Returns the user that is in this DM channel."""
        return self.recipients[0]

recipient: models.User property

Returns the user that is in this DM channel.

DMChannel

Bases: BaseChannel, MessageableMixin

Source code in interactions/models/discord/channel.py
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
@attrs.define(eq=False, order=False, hash=False, slots=False, kw_only=True)
class DMChannel(BaseChannel, MessageableMixin):
    recipients: List["models.User"] = attrs.field(repr=False, factory=list)
    """The users of the DM that will receive messages sent"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data = super()._process_dict(data, client)
        if recipients := data.get("recipients", None):
            data["recipients"] = [
                client.cache.place_user_data(recipient) if isinstance(recipient, dict) else recipient
                for recipient in recipients
            ]
        return data

    @property
    def members(self) -> List["models.User"]:
        """Returns a list of users that are in this DM channel."""
        return self.recipients

members: List[models.User] property

Returns a list of users that are in this DM channel.

recipients: List[models.User] = attrs.field(repr=False, factory=list) class-attribute

The users of the DM that will receive messages sent

DMGroup

Bases: DMChannel

Source code in interactions/models/discord/channel.py
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class DMGroup(DMChannel):
    owner_id: Snowflake_Type = attrs.field(repr=True)
    """id of the creator of the group DM"""
    application_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None)
    """Application id of the group DM creator if it is bot-created"""

    async def edit(
        self,
        *,
        name: Absent[str] = MISSING,
        icon: Absent[UPLOADABLE_TYPE] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "DMGroup":
        """
        Edit this DM Channel.

        Args:
            name: 1-100 character channel name
            icon: An icon to use
            reason: The reason for this change

        """
        return await super().edit(name=name, icon=icon, reason=reason, **kwargs)

    async def fetch_owner(self, *, force: bool = False) -> "models.User":
        """
        Fetch the owner of this DM group

        Args:
            force: Whether to force a fetch from the API

        """
        return await self._client.cache.fetch_user(self.owner_id, force=force)

    def get_owner(self) -> "models.User":
        """Get the owner of this DM group"""
        return self._client.cache.get_user(self.owner_id)

    async def add_recipient(
        self,
        user: Union["models.User", Snowflake_Type],
        access_token: str,
        nickname: Absent[Optional[str]] = MISSING,
    ) -> None:
        """
        Add a recipient to this DM Group.

        Args:
            user: The user to add
            access_token: access token of a user that has granted your app the gdm.join scope
            nickname: nickname to apply to the user being added

        """
        user = await self._client.cache.fetch_user(user)
        await self._client.http.group_dm_add_recipient(self.id, user.id, access_token, nickname)
        self.recipients.append(user)

    async def remove_recipient(self, user: Union["models.User", Snowflake_Type]) -> None:
        """
        Remove a recipient from this DM Group.

        Args:
            user: The user to remove

        """
        user = await self._client.cache.fetch_user(user)
        await self._client.http.group_dm_remove_recipient(self.id, user.id)
        self.recipients.remove(user)

application_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

Application id of the group DM creator if it is bot-created

owner_id: Snowflake_Type = attrs.field(repr=True) class-attribute

id of the creator of the group DM

add_recipient(user, access_token, nickname=MISSING) async

Add a recipient to this DM Group.

Parameters:

Name Type Description Default
user Union[User, Snowflake_Type]

The user to add

required
access_token str

access token of a user that has granted your app the gdm.join scope

required
nickname Absent[Optional[str]]

nickname to apply to the user being added

MISSING
Source code in interactions/models/discord/channel.py
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
async def add_recipient(
    self,
    user: Union["models.User", Snowflake_Type],
    access_token: str,
    nickname: Absent[Optional[str]] = MISSING,
) -> None:
    """
    Add a recipient to this DM Group.

    Args:
        user: The user to add
        access_token: access token of a user that has granted your app the gdm.join scope
        nickname: nickname to apply to the user being added

    """
    user = await self._client.cache.fetch_user(user)
    await self._client.http.group_dm_add_recipient(self.id, user.id, access_token, nickname)
    self.recipients.append(user)

edit(*, name=MISSING, icon=MISSING, reason=MISSING, **kwargs) async

Edit this DM Channel.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
icon Absent[UPLOADABLE_TYPE]

An icon to use

MISSING
reason Absent[str]

The reason for this change

MISSING
Source code in interactions/models/discord/channel.py
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
async def edit(
    self,
    *,
    name: Absent[str] = MISSING,
    icon: Absent[UPLOADABLE_TYPE] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "DMGroup":
    """
    Edit this DM Channel.

    Args:
        name: 1-100 character channel name
        icon: An icon to use
        reason: The reason for this change

    """
    return await super().edit(name=name, icon=icon, reason=reason, **kwargs)

fetch_owner(*, force=False) async

Fetch the owner of this DM group

Parameters:

Name Type Description Default
force bool

Whether to force a fetch from the API

False
Source code in interactions/models/discord/channel.py
973
974
975
976
977
978
979
980
981
async def fetch_owner(self, *, force: bool = False) -> "models.User":
    """
    Fetch the owner of this DM group

    Args:
        force: Whether to force a fetch from the API

    """
    return await self._client.cache.fetch_user(self.owner_id, force=force)

get_owner()

Get the owner of this DM group

Source code in interactions/models/discord/channel.py
983
984
985
def get_owner(self) -> "models.User":
    """Get the owner of this DM group"""
    return self._client.cache.get_user(self.owner_id)

remove_recipient(user) async

Remove a recipient from this DM Group.

Parameters:

Name Type Description Default
user Union[User, Snowflake_Type]

The user to remove

required
Source code in interactions/models/discord/channel.py
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
async def remove_recipient(self, user: Union["models.User", Snowflake_Type]) -> None:
    """
    Remove a recipient from this DM Group.

    Args:
        user: The user to remove

    """
    user = await self._client.cache.fetch_user(user)
    await self._client.http.group_dm_remove_recipient(self.id, user.id)
    self.recipients.remove(user)

GuildCategory

Bases: GuildChannel

Source code in interactions/models/discord/channel.py
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class GuildCategory(GuildChannel):
    @property
    def channels(self) -> List["TYPE_GUILD_CHANNEL"]:
        """Get all channels within the category"""
        return [channel for channel in self.guild.channels if channel.parent_id == self.id]

    @property
    def voice_channels(self) -> List["GuildVoice"]:
        """Get all voice channels within the category"""
        return [
            channel
            for channel in self.channels
            if isinstance(channel, GuildVoice) and not isinstance(channel, GuildStageVoice)
        ]

    @property
    def stage_channels(self) -> List["GuildStageVoice"]:
        """Get all stage channels within the category"""
        return [channel for channel in self.channels if isinstance(channel, GuildStageVoice)]

    @property
    def text_channels(self) -> List["GuildText"]:
        """Get all text channels within the category"""
        return [channel for channel in self.channels if isinstance(channel, GuildText)]

    @property
    def news_channels(self) -> List["GuildNews"]:
        """Get all news channels within the category"""
        return [channel for channel in self.channels if isinstance(channel, GuildNews)]

    async def edit(
        self,
        *,
        name: Absent[str] = MISSING,
        position: Absent[int] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "GuildCategory":
        """
        Edit this channel.

        Args:
            name: 1-100 character channel name
            position: the position of the channel in the left-hand listing
            permission_overwrites: channel or category-specific permissions
            reason: the reason for this change

        Returns:
            The updated channel object.

        """
        return await super().edit(
            name=name,
            position=position,
            permission_overwrites=permission_overwrites,
            reason=reason,
            **kwargs,
        )

    async def create_channel(
        self,
        channel_type: Union[ChannelType, int],
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        nsfw: bool = False,
        bitrate: int = 64000,
        user_limit: int = 0,
        rate_limit_per_user: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "TYPE_GUILD_CHANNEL":
        """
        Create a guild channel within this category, allows for explicit channel type setting.

        Args:
            channel_type: The type of channel to create
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            nsfw: Should this channel be marked nsfw
            bitrate: The bitrate of this channel, only for voice
            user_limit: The max users that can be in this channel, only for voice
            rate_limit_per_user: The time users must wait between sending messages
            reason: The reason for creating this channel

        Returns:
            The newly created channel.

        """
        return await self.guild.create_channel(
            channel_type=channel_type,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            category=self.id,
            nsfw=nsfw,
            bitrate=bitrate,
            user_limit=user_limit,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
        )

    async def create_text_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        nsfw: bool = False,
        rate_limit_per_user: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "GuildText":
        """
        Create a text channel in this guild within this category.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            nsfw: Should this channel be marked nsfw
            rate_limit_per_user: The time users must wait between sending messages
            reason: The reason for creating this channel

        Returns:
           The newly created text channel.

        """
        return await self.create_channel(
            channel_type=ChannelType.GUILD_TEXT,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            nsfw=nsfw,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
        )

    async def create_news_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        nsfw: bool = False,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "GuildNews":
        """
        Create a news channel in this guild within this category.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            nsfw: Should this channel be marked nsfw
            reason: The reason for creating this channel

        Returns:
           The newly created news channel.

        """
        return await self.create_channel(
            channel_type=ChannelType.GUILD_NEWS,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            nsfw=nsfw,
            reason=reason,
        )

    async def create_voice_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        nsfw: bool = False,
        bitrate: int = 64000,
        user_limit: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "GuildVoice":
        """
        Create a guild voice channel within this category.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            nsfw: Should this channel be marked nsfw
            bitrate: The bitrate of this channel, only for voice
            user_limit: The max users that can be in this channel, only for voice
            reason: The reason for creating this channel

        Returns:
           The newly created voice channel.

        """
        return await self.create_channel(
            channel_type=ChannelType.GUILD_VOICE,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            nsfw=nsfw,
            bitrate=bitrate,
            user_limit=user_limit,
            reason=reason,
        )

    async def create_stage_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        bitrate: int = 64000,
        user_limit: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "GuildStageVoice":
        """
        Create a guild stage channel within this category.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            bitrate: The bitrate of this channel, only for voice
            user_limit: The max users that can be in this channel, only for voice
            reason: The reason for creating this channel

        Returns:
            The newly created stage channel.

        """
        return await self.create_channel(
            channel_type=ChannelType.GUILD_STAGE_VOICE,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            bitrate=bitrate,
            user_limit=user_limit,
            reason=reason,
        )

channels: List[TYPE_GUILD_CHANNEL] property

Get all channels within the category

news_channels: List[GuildNews] property

Get all news channels within the category

stage_channels: List[GuildStageVoice] property

Get all stage channels within the category

text_channels: List[GuildText] property

Get all text channels within the category

voice_channels: List[GuildVoice] property

Get all voice channels within the category

create_channel(channel_type, name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, nsfw=False, bitrate=64000, user_limit=0, rate_limit_per_user=0, reason=MISSING) async

Create a guild channel within this category, allows for explicit channel type setting.

Parameters:

Name Type Description Default
channel_type Union[ChannelType, int]

The type of channel to create

required
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
nsfw bool

Should this channel be marked nsfw

False
bitrate int

The bitrate of this channel, only for voice

64000
user_limit int

The max users that can be in this channel, only for voice

0
rate_limit_per_user int

The time users must wait between sending messages

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
TYPE_GUILD_CHANNEL

The newly created channel.

Source code in interactions/models/discord/channel.py
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
async def create_channel(
    self,
    channel_type: Union[ChannelType, int],
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    nsfw: bool = False,
    bitrate: int = 64000,
    user_limit: int = 0,
    rate_limit_per_user: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "TYPE_GUILD_CHANNEL":
    """
    Create a guild channel within this category, allows for explicit channel type setting.

    Args:
        channel_type: The type of channel to create
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        nsfw: Should this channel be marked nsfw
        bitrate: The bitrate of this channel, only for voice
        user_limit: The max users that can be in this channel, only for voice
        rate_limit_per_user: The time users must wait between sending messages
        reason: The reason for creating this channel

    Returns:
        The newly created channel.

    """
    return await self.guild.create_channel(
        channel_type=channel_type,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        category=self.id,
        nsfw=nsfw,
        bitrate=bitrate,
        user_limit=user_limit,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
    )

create_news_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, nsfw=False, reason=MISSING) async

Create a news channel in this guild within this category.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
nsfw bool

Should this channel be marked nsfw

False
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildNews

The newly created news channel.

Source code in interactions/models/discord/channel.py
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
async def create_news_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    nsfw: bool = False,
    reason: Absent[Optional[str]] = MISSING,
) -> "GuildNews":
    """
    Create a news channel in this guild within this category.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        nsfw: Should this channel be marked nsfw
        reason: The reason for creating this channel

    Returns:
       The newly created news channel.

    """
    return await self.create_channel(
        channel_type=ChannelType.GUILD_NEWS,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        nsfw=nsfw,
        reason=reason,
    )

create_stage_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, bitrate=64000, user_limit=0, reason=MISSING) async

Create a guild stage channel within this category.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
bitrate int

The bitrate of this channel, only for voice

64000
user_limit int

The max users that can be in this channel, only for voice

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildStageVoice

The newly created stage channel.

Source code in interactions/models/discord/channel.py
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
async def create_stage_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    bitrate: int = 64000,
    user_limit: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "GuildStageVoice":
    """
    Create a guild stage channel within this category.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        bitrate: The bitrate of this channel, only for voice
        user_limit: The max users that can be in this channel, only for voice
        reason: The reason for creating this channel

    Returns:
        The newly created stage channel.

    """
    return await self.create_channel(
        channel_type=ChannelType.GUILD_STAGE_VOICE,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        bitrate=bitrate,
        user_limit=user_limit,
        reason=reason,
    )

create_text_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, nsfw=False, rate_limit_per_user=0, reason=MISSING) async

Create a text channel in this guild within this category.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
nsfw bool

Should this channel be marked nsfw

False
rate_limit_per_user int

The time users must wait between sending messages

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildText

The newly created text channel.

Source code in interactions/models/discord/channel.py
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
async def create_text_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    nsfw: bool = False,
    rate_limit_per_user: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "GuildText":
    """
    Create a text channel in this guild within this category.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        nsfw: Should this channel be marked nsfw
        rate_limit_per_user: The time users must wait between sending messages
        reason: The reason for creating this channel

    Returns:
       The newly created text channel.

    """
    return await self.create_channel(
        channel_type=ChannelType.GUILD_TEXT,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        nsfw=nsfw,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
    )

create_voice_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, nsfw=False, bitrate=64000, user_limit=0, reason=MISSING) async

Create a guild voice channel within this category.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
nsfw bool

Should this channel be marked nsfw

False
bitrate int

The bitrate of this channel, only for voice

64000
user_limit int

The max users that can be in this channel, only for voice

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildVoice

The newly created voice channel.

Source code in interactions/models/discord/channel.py
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
async def create_voice_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    nsfw: bool = False,
    bitrate: int = 64000,
    user_limit: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "GuildVoice":
    """
    Create a guild voice channel within this category.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        nsfw: Should this channel be marked nsfw
        bitrate: The bitrate of this channel, only for voice
        user_limit: The max users that can be in this channel, only for voice
        reason: The reason for creating this channel

    Returns:
       The newly created voice channel.

    """
    return await self.create_channel(
        channel_type=ChannelType.GUILD_VOICE,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        nsfw=nsfw,
        bitrate=bitrate,
        user_limit=user_limit,
        reason=reason,
    )

edit(*, name=MISSING, position=MISSING, permission_overwrites=MISSING, reason=MISSING, **kwargs) async

Edit this channel.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
position Absent[int]

the position of the channel in the left-hand listing

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

channel or category-specific permissions

MISSING
reason Absent[str]

the reason for this change

MISSING

Returns:

Type Description
GuildCategory

The updated channel object.

Source code in interactions/models/discord/channel.py
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
async def edit(
    self,
    *,
    name: Absent[str] = MISSING,
    position: Absent[int] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "GuildCategory":
    """
    Edit this channel.

    Args:
        name: 1-100 character channel name
        position: the position of the channel in the left-hand listing
        permission_overwrites: channel or category-specific permissions
        reason: the reason for this change

    Returns:
        The updated channel object.

    """
    return await super().edit(
        name=name,
        position=position,
        permission_overwrites=permission_overwrites,
        reason=reason,
        **kwargs,
    )

GuildChannel

Bases: BaseChannel

Source code in interactions/models/discord/channel.py
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
@attrs.define(eq=False, order=False, hash=False, slots=False, kw_only=True)
class GuildChannel(BaseChannel):
    position: Optional[int] = attrs.field(repr=False, default=0)
    """Sorting position of the channel"""
    nsfw: bool = attrs.field(repr=False, default=False)
    """Whether the channel is nsfw"""
    parent_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake))
    """id of the parent category for a channel (each parent category can contain up to 50 channels)"""
    permission_overwrites: list[PermissionOverwrite] = attrs.field(repr=False, factory=list)
    """A list of the overwritten permissions for the members and roles"""

    _guild_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake))

    @property
    def guild(self) -> "models.Guild":
        """The guild this channel belongs to."""
        return self._client.cache.get_guild(self._guild_id)

    @property
    def category(self) -> Optional["GuildCategory"]:
        """The parent category of this channel."""
        return self._client.cache.get_channel(self.parent_id)

    @property
    def gui_position(self) -> int:
        """The position of this channel in the Discord interface."""
        return self.guild.get_channel_gui_position(self.id)

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data = super()._process_dict(data, client)
        if overwrites := data.get("permission_overwrites"):
            data["permission_overwrites"] = PermissionOverwrite.from_list(overwrites)
        return data

    def permissions_for(self, instance: Snowflake_Type) -> Permissions:
        """
        Calculates permissions for an instance

        Args:
            instance: Member or Role instance (or its ID)

        Returns:
            Permissions data

        Raises:
            ValueError: If could not find any member or role by given ID
            RuntimeError: If given instance is from another guild

        """
        if (is_member := isinstance(instance, models.Member)) or isinstance(instance, models.Role):
            if instance._guild_id != self._guild_id:
                raise RuntimeError("Unable to calculate permissions for the instance from different guild")

            if is_member:
                return instance.channel_permissions(self)

            permissions = instance.permissions

            for overwrite in self.permission_overwrites:
                if overwrite.id == instance.id:
                    permissions &= ~overwrite.deny
                    permissions |= overwrite.allow
                    break

            return permissions

        instance = to_snowflake(instance)
        guild = self.guild
        if instance := guild.get_member(instance) or guild.get_role(instance):
            return self.permissions_for(instance)
        raise ValueError("Unable to find any member or role by given instance ID")

    async def add_permission(
        self,
        target: Union["PermissionOverwrite", "models.Role", "models.User", "models.Member", "Snowflake_Type"],
        type: Optional["OverwriteType"] = None,
        allow: Optional[List["Permissions"] | int] = None,
        deny: Optional[List["Permissions"] | int] = None,
        reason: Optional[str] = None,
    ) -> None:
        """
        Add a permission to this channel.

        Args:
            target: The updated PermissionOverwrite object, or the Role or User object/id to update
            type: The type of permission overwrite. Only applicable if target is an id
            allow: List of permissions to allow. Only applicable if target is not an PermissionOverwrite object
            deny: List of permissions to deny. Only applicable if target is not an PermissionOverwrite object
            reason: The reason for this change

        Raises:
            ValueError: Invalid target for permission

        """
        allow = allow or []
        deny = deny or []
        if not isinstance(target, PermissionOverwrite):
            if isinstance(target, (models.User, models.Member)):
                target = target.id
                type = OverwriteType.MEMBER
            elif isinstance(target, models.Role):
                target = target.id
                type = OverwriteType.ROLE
            elif type and isinstance(target, Snowflake_Type):
                target = to_snowflake(target)
            else:
                raise ValueError("Invalid target and/or type for permission")
            overwrite = PermissionOverwrite(id=target, type=type, allow=Permissions.NONE, deny=Permissions.NONE)
            if isinstance(allow, int):
                overwrite.allow |= allow
            else:
                for perm in allow:
                    overwrite.allow |= perm
            if isinstance(deny, int):
                overwrite.deny |= deny
            else:
                for perm in deny:
                    overwrite.deny |= perm
        else:
            overwrite = target

        if exists := get(self.permission_overwrites, id=overwrite.id, type=overwrite.type):
            exists.deny = (exists.deny | overwrite.deny) & ~overwrite.allow
            exists.allow = (exists.allow | overwrite.allow) & ~overwrite.deny
            await self.edit_permission(exists, reason)
        else:
            permission_overwrites = self.permission_overwrites
            permission_overwrites.append(overwrite)
            await self.edit(permission_overwrites=permission_overwrites)

    async def edit_permission(self, overwrite: PermissionOverwrite, reason: Optional[str] = None) -> None:
        """
        Edit the permissions for this channel.

        Args:
            overwrite: The permission overwrite to apply
            reason: The reason for this change

        """
        await self._client.http.edit_channel_permission(
            self.id,
            overwrite_id=overwrite.id,
            allow=overwrite.allow,
            deny=overwrite.deny,
            perm_type=overwrite.type,
            reason=reason,
        )

    async def delete_permission(
        self,
        target: Union["PermissionOverwrite", "models.Role", "models.User"],
        reason: Absent[Optional[str]] = MISSING,
    ) -> None:
        """
        Delete a permission overwrite for this channel.

        Args:
            target: The permission overwrite to delete
            reason: The reason for this change

        """
        target = to_snowflake(target)
        await self._client.http.delete_channel_permission(self.id, target, reason)

    async def set_permission(
        self,
        target: Union["models.Role", "models.Member", "models.User"],
        *,
        add_reactions: bool | None = None,
        administrator: bool | None = None,
        attach_files: bool | None = None,
        ban_members: bool | None = None,
        change_nickname: bool | None = None,
        connect: bool | None = None,
        create_instant_invite: bool | None = None,
        deafen_members: bool | None = None,
        embed_links: bool | None = None,
        kick_members: bool | None = None,
        manage_channels: bool | None = None,
        manage_emojis_and_stickers: bool | None = None,
        manage_events: bool | None = None,
        manage_guild: bool | None = None,
        manage_messages: bool | None = None,
        manage_nicknames: bool | None = None,
        manage_roles: bool | None = None,
        manage_threads: bool | None = None,
        manage_webhooks: bool | None = None,
        mention_everyone: bool | None = None,
        moderate_members: bool | None = None,
        move_members: bool | None = None,
        mute_members: bool | None = None,
        priority_speaker: bool | None = None,
        read_message_history: bool | None = None,
        request_to_speak: bool | None = None,
        send_messages: bool | None = None,
        send_messages_in_threads: bool | None = None,
        send_tts_messages: bool | None = None,
        speak: bool | None = None,
        start_embedded_activities: bool | None = None,
        stream: bool | None = None,
        use_application_commands: bool | None = None,
        use_external_emojis: bool | None = None,
        use_external_stickers: bool | None = None,
        use_private_threads: bool | None = None,
        use_public_threads: bool | None = None,
        use_vad: bool | None = None,
        view_audit_log: bool | None = None,
        view_channel: bool | None = None,
        view_guild_insights: bool | None = None,
        reason: str | None = None,
    ) -> None:
        """
        Set the Permission Overwrites for a given target.

        Args:
            target: The target to set permission overwrites for
            add_reactions: Allows for the addition of reactions to messages
            administrator: Allows all permissions and bypasses channel permission overwrites
            attach_files: Allows for uploading images and files
            ban_members: Allows banning members
            change_nickname: Allows for modification of own nickname
            connect: Allows for joining of a voice channel
            create_instant_invite: Allows creation of instant invites
            deafen_members: Allows for deafening of members in a voice channel
            embed_links: Links sent by users with this permission will be auto-embedded
            kick_members: Allows kicking members
            manage_channels: Allows management and editing of channels
            manage_emojis_and_stickers: Allows management and editing of emojis and stickers
            manage_events: Allows for creating, editing, and deleting scheduled events
            manage_guild: Allows management and editing of the guild
            manage_messages: Allows for deletion of other users messages
            manage_nicknames: Allows for modification of other users nicknames
            manage_roles: Allows management and editing of roles
            manage_threads: Allows for deleting and archiving threads, and viewing all private threads
            manage_webhooks: Allows management and editing of webhooks
            mention_everyone: Allows for using the `@everyone` tag to notify all users in a channel, and the `@here` tag to notify all online users in a channel
            moderate_members: Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels
            move_members: Allows for moving of members between voice channels
            mute_members: Allows for muting members in a voice channel
            priority_speaker: Allows for using priority speaker in a voice channel
            read_message_history: Allows for reading of message history
            request_to_speak: Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.)
            send_messages:  Allows for sending messages in a channel (does not allow sending messages in threads)
            send_messages_in_threads: Allows for sending messages in threads
            send_tts_messages:  Allows for sending of `/tts` messages
            speak: Allows for speaking in a voice channel
            start_embedded_activities: Allows for using Activities (applications with the `EMBEDDED` flag) in a voice channel
            stream: Allows the user to go live
            use_application_commands: Allows members to use application commands, including slash commands and context menu commands
            use_external_emojis: Allows the usage of custom emojis from other servers
            use_external_stickers: Allows the usage of custom stickers from other servers
            use_private_threads: Allows for creating private threads
            use_public_threads:  Allows for creating public and announcement threads
            use_vad: Allows for using voice-activity-detection in a voice channel
            view_audit_log: Allows for viewing of audit logs
            view_channel: Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels
            view_guild_insights: Allows for viewing guild insights
            reason: The reason for creating this overwrite

        """
        overwrite = PermissionOverwrite.for_target(target)

        allow: Permissions = Permissions.NONE
        deny: Permissions = Permissions.NONE

        for name, val in locals().items():
            if isinstance(val, bool):
                if val:
                    allow |= getattr(Permissions, name.upper())
                else:
                    deny |= getattr(Permissions, name.upper())

        overwrite.add_allows(allow)
        overwrite.add_denies(deny)

        await self.edit_permission(overwrite, reason)

    @property
    def members(self) -> List["models.Member"]:
        """Returns a list of members that can see this channel."""
        return [m for m in self.guild.members if Permissions.VIEW_CHANNEL in m.channel_permissions(self)]  # type: ignore

    @property
    def bots(self) -> List["models.Member"]:
        """Returns a list of bots that can see this channel."""
        return [m for m in self.guild.members if m.bot and Permissions.VIEW_CHANNEL in m.channel_permissions(self)]  # type: ignore

    @property
    def humans(self) -> List["models.Member"]:
        """Returns a list of humans that can see this channel."""
        return [m for m in self.guild.members if not m.bot and Permissions.VIEW_CHANNEL in m.channel_permissions(self)]  # type: ignore

    async def clone(self, name: Optional[str] = None, reason: Absent[Optional[str]] = MISSING) -> "TYPE_GUILD_CHANNEL":
        """
        Clone this channel and create a new one.

        Args:
            name: The name of the new channel. Defaults to the current name
            reason: The reason for creating this channel

        Returns:
            The newly created channel.

        """
        return await self.guild.create_channel(
            channel_type=self.type,
            name=name or self.name,
            topic=getattr(self, "topic", MISSING),
            position=self.position,
            permission_overwrites=self.permission_overwrites,
            category=self.category,
            nsfw=self.nsfw,
            bitrate=getattr(self, "bitrate", 64000),
            user_limit=getattr(self, "user_limit", 0),
            rate_limit_per_user=getattr(self, "rate_limit_per_user", 0),
            reason=reason,
        )

bots: List[models.Member] property

Returns a list of bots that can see this channel.

category: Optional[GuildCategory] property

The parent category of this channel.

gui_position: int property

The position of this channel in the Discord interface.

guild: models.Guild property

The guild this channel belongs to.

humans: List[models.Member] property

Returns a list of humans that can see this channel.

members: List[models.Member] property

Returns a list of members that can see this channel.

nsfw: bool = attrs.field(repr=False, default=False) class-attribute

Whether the channel is nsfw

parent_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake)) class-attribute

id of the parent category for a channel (each parent category can contain up to 50 channels)

permission_overwrites: list[PermissionOverwrite] = attrs.field(repr=False, factory=list) class-attribute

A list of the overwritten permissions for the members and roles

position: Optional[int] = attrs.field(repr=False, default=0) class-attribute

Sorting position of the channel

add_permission(target, type=None, allow=None, deny=None, reason=None) async

Add a permission to this channel.

Parameters:

Name Type Description Default
target Union[PermissionOverwrite, Role, User, Member, Snowflake_Type]

The updated PermissionOverwrite object, or the Role or User object/id to update

required
type Optional[OverwriteType]

The type of permission overwrite. Only applicable if target is an id

None
allow Optional[List[Permissions] | int]

List of permissions to allow. Only applicable if target is not an PermissionOverwrite object

None
deny Optional[List[Permissions] | int]

List of permissions to deny. Only applicable if target is not an PermissionOverwrite object

None
reason Optional[str]

The reason for this change

None

Raises:

Type Description
ValueError

Invalid target for permission

Source code in interactions/models/discord/channel.py
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
async def add_permission(
    self,
    target: Union["PermissionOverwrite", "models.Role", "models.User", "models.Member", "Snowflake_Type"],
    type: Optional["OverwriteType"] = None,
    allow: Optional[List["Permissions"] | int] = None,
    deny: Optional[List["Permissions"] | int] = None,
    reason: Optional[str] = None,
) -> None:
    """
    Add a permission to this channel.

    Args:
        target: The updated PermissionOverwrite object, or the Role or User object/id to update
        type: The type of permission overwrite. Only applicable if target is an id
        allow: List of permissions to allow. Only applicable if target is not an PermissionOverwrite object
        deny: List of permissions to deny. Only applicable if target is not an PermissionOverwrite object
        reason: The reason for this change

    Raises:
        ValueError: Invalid target for permission

    """
    allow = allow or []
    deny = deny or []
    if not isinstance(target, PermissionOverwrite):
        if isinstance(target, (models.User, models.Member)):
            target = target.id
            type = OverwriteType.MEMBER
        elif isinstance(target, models.Role):
            target = target.id
            type = OverwriteType.ROLE
        elif type and isinstance(target, Snowflake_Type):
            target = to_snowflake(target)
        else:
            raise ValueError("Invalid target and/or type for permission")
        overwrite = PermissionOverwrite(id=target, type=type, allow=Permissions.NONE, deny=Permissions.NONE)
        if isinstance(allow, int):
            overwrite.allow |= allow
        else:
            for perm in allow:
                overwrite.allow |= perm
        if isinstance(deny, int):
            overwrite.deny |= deny
        else:
            for perm in deny:
                overwrite.deny |= perm
    else:
        overwrite = target

    if exists := get(self.permission_overwrites, id=overwrite.id, type=overwrite.type):
        exists.deny = (exists.deny | overwrite.deny) & ~overwrite.allow
        exists.allow = (exists.allow | overwrite.allow) & ~overwrite.deny
        await self.edit_permission(exists, reason)
    else:
        permission_overwrites = self.permission_overwrites
        permission_overwrites.append(overwrite)
        await self.edit(permission_overwrites=permission_overwrites)

clone(name=None, reason=MISSING) async

Clone this channel and create a new one.

Parameters:

Name Type Description Default
name Optional[str]

The name of the new channel. Defaults to the current name

None
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
TYPE_GUILD_CHANNEL

The newly created channel.

Source code in interactions/models/discord/channel.py
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
async def clone(self, name: Optional[str] = None, reason: Absent[Optional[str]] = MISSING) -> "TYPE_GUILD_CHANNEL":
    """
    Clone this channel and create a new one.

    Args:
        name: The name of the new channel. Defaults to the current name
        reason: The reason for creating this channel

    Returns:
        The newly created channel.

    """
    return await self.guild.create_channel(
        channel_type=self.type,
        name=name or self.name,
        topic=getattr(self, "topic", MISSING),
        position=self.position,
        permission_overwrites=self.permission_overwrites,
        category=self.category,
        nsfw=self.nsfw,
        bitrate=getattr(self, "bitrate", 64000),
        user_limit=getattr(self, "user_limit", 0),
        rate_limit_per_user=getattr(self, "rate_limit_per_user", 0),
        reason=reason,
    )

delete_permission(target, reason=MISSING) async

Delete a permission overwrite for this channel.

Parameters:

Name Type Description Default
target Union[PermissionOverwrite, Role, User]

The permission overwrite to delete

required
reason Absent[Optional[str]]

The reason for this change

MISSING
Source code in interactions/models/discord/channel.py
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
async def delete_permission(
    self,
    target: Union["PermissionOverwrite", "models.Role", "models.User"],
    reason: Absent[Optional[str]] = MISSING,
) -> None:
    """
    Delete a permission overwrite for this channel.

    Args:
        target: The permission overwrite to delete
        reason: The reason for this change

    """
    target = to_snowflake(target)
    await self._client.http.delete_channel_permission(self.id, target, reason)

edit_permission(overwrite, reason=None) async

Edit the permissions for this channel.

Parameters:

Name Type Description Default
overwrite PermissionOverwrite

The permission overwrite to apply

required
reason Optional[str]

The reason for this change

None
Source code in interactions/models/discord/channel.py
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
async def edit_permission(self, overwrite: PermissionOverwrite, reason: Optional[str] = None) -> None:
    """
    Edit the permissions for this channel.

    Args:
        overwrite: The permission overwrite to apply
        reason: The reason for this change

    """
    await self._client.http.edit_channel_permission(
        self.id,
        overwrite_id=overwrite.id,
        allow=overwrite.allow,
        deny=overwrite.deny,
        perm_type=overwrite.type,
        reason=reason,
    )

permissions_for(instance)

Calculates permissions for an instance

Parameters:

Name Type Description Default
instance Snowflake_Type

Member or Role instance (or its ID)

required

Returns:

Type Description
Permissions

Permissions data

Raises:

Type Description
ValueError

If could not find any member or role by given ID

RuntimeError

If given instance is from another guild

Source code in interactions/models/discord/channel.py
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
def permissions_for(self, instance: Snowflake_Type) -> Permissions:
    """
    Calculates permissions for an instance

    Args:
        instance: Member or Role instance (or its ID)

    Returns:
        Permissions data

    Raises:
        ValueError: If could not find any member or role by given ID
        RuntimeError: If given instance is from another guild

    """
    if (is_member := isinstance(instance, models.Member)) or isinstance(instance, models.Role):
        if instance._guild_id != self._guild_id:
            raise RuntimeError("Unable to calculate permissions for the instance from different guild")

        if is_member:
            return instance.channel_permissions(self)

        permissions = instance.permissions

        for overwrite in self.permission_overwrites:
            if overwrite.id == instance.id:
                permissions &= ~overwrite.deny
                permissions |= overwrite.allow
                break

        return permissions

    instance = to_snowflake(instance)
    guild = self.guild
    if instance := guild.get_member(instance) or guild.get_role(instance):
        return self.permissions_for(instance)
    raise ValueError("Unable to find any member or role by given instance ID")

set_permission(target, *, add_reactions=None, administrator=None, attach_files=None, ban_members=None, change_nickname=None, connect=None, create_instant_invite=None, deafen_members=None, embed_links=None, kick_members=None, manage_channels=None, manage_emojis_and_stickers=None, manage_events=None, manage_guild=None, manage_messages=None, manage_nicknames=None, manage_roles=None, manage_threads=None, manage_webhooks=None, mention_everyone=None, moderate_members=None, move_members=None, mute_members=None, priority_speaker=None, read_message_history=None, request_to_speak=None, send_messages=None, send_messages_in_threads=None, send_tts_messages=None, speak=None, start_embedded_activities=None, stream=None, use_application_commands=None, use_external_emojis=None, use_external_stickers=None, use_private_threads=None, use_public_threads=None, use_vad=None, view_audit_log=None, view_channel=None, view_guild_insights=None, reason=None) async

Set the Permission Overwrites for a given target.

Parameters:

Name Type Description Default
target Union[Role, Member, User]

The target to set permission overwrites for

required
add_reactions bool | None

Allows for the addition of reactions to messages

None
administrator bool | None

Allows all permissions and bypasses channel permission overwrites

None
attach_files bool | None

Allows for uploading images and files

None
ban_members bool | None

Allows banning members

None
change_nickname bool | None

Allows for modification of own nickname

None
connect bool | None

Allows for joining of a voice channel

None
create_instant_invite bool | None

Allows creation of instant invites

None
deafen_members bool | None

Allows for deafening of members in a voice channel

None
embed_links bool | None

Links sent by users with this permission will be auto-embedded

None
kick_members bool | None

Allows kicking members

None
manage_channels bool | None

Allows management and editing of channels

None
manage_emojis_and_stickers bool | None

Allows management and editing of emojis and stickers

None
manage_events bool | None

Allows for creating, editing, and deleting scheduled events

None
manage_guild bool | None

Allows management and editing of the guild

None
manage_messages bool | None

Allows for deletion of other users messages

None
manage_nicknames bool | None

Allows for modification of other users nicknames

None
manage_roles bool | None

Allows management and editing of roles

None
manage_threads bool | None

Allows for deleting and archiving threads, and viewing all private threads

None
manage_webhooks bool | None

Allows management and editing of webhooks

None
mention_everyone bool | None

Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all online users in a channel

None
moderate_members bool | None

Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels

None
move_members bool | None

Allows for moving of members between voice channels

None
mute_members bool | None

Allows for muting members in a voice channel

None
priority_speaker bool | None

Allows for using priority speaker in a voice channel

None
read_message_history bool | None

Allows for reading of message history

None
request_to_speak bool | None

Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.)

None
send_messages bool | None

Allows for sending messages in a channel (does not allow sending messages in threads)

None
send_messages_in_threads bool | None

Allows for sending messages in threads

None
send_tts_messages bool | None

Allows for sending of /tts messages

None
speak bool | None

Allows for speaking in a voice channel

None
start_embedded_activities bool | None

Allows for using Activities (applications with the EMBEDDED flag) in a voice channel

None
stream bool | None

Allows the user to go live

None
use_application_commands bool | None

Allows members to use application commands, including slash commands and context menu commands

None
use_external_emojis bool | None

Allows the usage of custom emojis from other servers

None
use_external_stickers bool | None

Allows the usage of custom stickers from other servers

None
use_private_threads bool | None

Allows for creating private threads

None
use_public_threads bool | None

Allows for creating public and announcement threads

None
use_vad bool | None

Allows for using voice-activity-detection in a voice channel

None
view_audit_log bool | None

Allows for viewing of audit logs

None
view_channel bool | None

Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels

None
view_guild_insights bool | None

Allows for viewing guild insights

None
reason str | None

The reason for creating this overwrite

None
Source code in interactions/models/discord/channel.py
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
async def set_permission(
    self,
    target: Union["models.Role", "models.Member", "models.User"],
    *,
    add_reactions: bool | None = None,
    administrator: bool | None = None,
    attach_files: bool | None = None,
    ban_members: bool | None = None,
    change_nickname: bool | None = None,
    connect: bool | None = None,
    create_instant_invite: bool | None = None,
    deafen_members: bool | None = None,
    embed_links: bool | None = None,
    kick_members: bool | None = None,
    manage_channels: bool | None = None,
    manage_emojis_and_stickers: bool | None = None,
    manage_events: bool | None = None,
    manage_guild: bool | None = None,
    manage_messages: bool | None = None,
    manage_nicknames: bool | None = None,
    manage_roles: bool | None = None,
    manage_threads: bool | None = None,
    manage_webhooks: bool | None = None,
    mention_everyone: bool | None = None,
    moderate_members: bool | None = None,
    move_members: bool | None = None,
    mute_members: bool | None = None,
    priority_speaker: bool | None = None,
    read_message_history: bool | None = None,
    request_to_speak: bool | None = None,
    send_messages: bool | None = None,
    send_messages_in_threads: bool | None = None,
    send_tts_messages: bool | None = None,
    speak: bool | None = None,
    start_embedded_activities: bool | None = None,
    stream: bool | None = None,
    use_application_commands: bool | None = None,
    use_external_emojis: bool | None = None,
    use_external_stickers: bool | None = None,
    use_private_threads: bool | None = None,
    use_public_threads: bool | None = None,
    use_vad: bool | None = None,
    view_audit_log: bool | None = None,
    view_channel: bool | None = None,
    view_guild_insights: bool | None = None,
    reason: str | None = None,
) -> None:
    """
    Set the Permission Overwrites for a given target.

    Args:
        target: The target to set permission overwrites for
        add_reactions: Allows for the addition of reactions to messages
        administrator: Allows all permissions and bypasses channel permission overwrites
        attach_files: Allows for uploading images and files
        ban_members: Allows banning members
        change_nickname: Allows for modification of own nickname
        connect: Allows for joining of a voice channel
        create_instant_invite: Allows creation of instant invites
        deafen_members: Allows for deafening of members in a voice channel
        embed_links: Links sent by users with this permission will be auto-embedded
        kick_members: Allows kicking members
        manage_channels: Allows management and editing of channels
        manage_emojis_and_stickers: Allows management and editing of emojis and stickers
        manage_events: Allows for creating, editing, and deleting scheduled events
        manage_guild: Allows management and editing of the guild
        manage_messages: Allows for deletion of other users messages
        manage_nicknames: Allows for modification of other users nicknames
        manage_roles: Allows management and editing of roles
        manage_threads: Allows for deleting and archiving threads, and viewing all private threads
        manage_webhooks: Allows management and editing of webhooks
        mention_everyone: Allows for using the `@everyone` tag to notify all users in a channel, and the `@here` tag to notify all online users in a channel
        moderate_members: Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels
        move_members: Allows for moving of members between voice channels
        mute_members: Allows for muting members in a voice channel
        priority_speaker: Allows for using priority speaker in a voice channel
        read_message_history: Allows for reading of message history
        request_to_speak: Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.)
        send_messages:  Allows for sending messages in a channel (does not allow sending messages in threads)
        send_messages_in_threads: Allows for sending messages in threads
        send_tts_messages:  Allows for sending of `/tts` messages
        speak: Allows for speaking in a voice channel
        start_embedded_activities: Allows for using Activities (applications with the `EMBEDDED` flag) in a voice channel
        stream: Allows the user to go live
        use_application_commands: Allows members to use application commands, including slash commands and context menu commands
        use_external_emojis: Allows the usage of custom emojis from other servers
        use_external_stickers: Allows the usage of custom stickers from other servers
        use_private_threads: Allows for creating private threads
        use_public_threads:  Allows for creating public and announcement threads
        use_vad: Allows for using voice-activity-detection in a voice channel
        view_audit_log: Allows for viewing of audit logs
        view_channel: Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels
        view_guild_insights: Allows for viewing guild insights
        reason: The reason for creating this overwrite

    """
    overwrite = PermissionOverwrite.for_target(target)

    allow: Permissions = Permissions.NONE
    deny: Permissions = Permissions.NONE

    for name, val in locals().items():
        if isinstance(val, bool):
            if val:
                allow |= getattr(Permissions, name.upper())
            else:
                deny |= getattr(Permissions, name.upper())

    overwrite.add_allows(allow)
    overwrite.add_denies(deny)

    await self.edit_permission(overwrite, reason)

GuildForum

Bases: GuildChannel, InvitableMixin

Source code in interactions/models/discord/channel.py
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class GuildForum(GuildChannel, InvitableMixin):
    available_tags: List[ThreadTag] = attrs.field(repr=False, factory=list)
    """A list of tags available to assign to threads"""
    default_reaction_emoji: Optional[DefaultReaction] = attrs.field(repr=False, default=None)
    """The default emoji to react with for posts"""
    last_message_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None)
    # TODO: Implement "template" once the API supports them
    rate_limit_per_user: int = attrs.field(repr=False, default=0)
    """Amount of seconds a user has to wait before sending another message (0-21600)"""
    default_sort_order: Optional[ForumSortOrder] = attrs.field(
        repr=False, default=None, converter=ForumSortOrder.converter
    )
    """the default sort order type used to order posts in GUILD_FORUM channels. Defaults to null, which indicates a preferred sort order hasn't been set by a channel admin"""
    default_forum_layout: ForumLayoutType = attrs.field(
        repr=False, default=ForumLayoutType.NOT_SET, converter=ForumLayoutType
    )
    """The default forum layout view used to display posts in GUILD_FORUM channels. Defaults to 0, which indicates a layout view has not been set by a channel admin"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data = super()._process_dict(data, client)
        data["available_tags"] = [
            ThreadTag.from_dict(tag_data | {"parent_channel_id": data["id"]}, client)
            for tag_data in data.get("available_tags", [])
        ]
        return data

    async def create_post(
        self,
        name: str,
        content: str | None,
        applied_tags: Absent[List[Union["Snowflake_Type", "ThreadTag", str]]] = MISSING,
        *,
        auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
        rate_limit_per_user: Absent[int] = MISSING,
        embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
        embed: Optional[Union["Embed", dict]] = None,
        components: Optional[
            Union[
                List[List[Union["BaseComponent", dict]]],
                List[Union["BaseComponent", dict]],
                "BaseComponent",
                dict,
            ]
        ] = None,
        stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
        allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
        files: Optional[Union["UPLOADABLE_TYPE", List["UPLOADABLE_TYPE"]]] = None,
        file: Optional["UPLOADABLE_TYPE"] = None,
        tts: bool = False,
        reason: Absent[str] = MISSING,
    ) -> "GuildForumPost":
        """
        Create a post within this channel.

        Args:
            name: The name of the post
            content: The text content of this post
            applied_tags: A list of tag ids or tag objects to apply to this post
            auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
            rate_limit_per_user: The time users must wait between sending messages
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            stickers: IDs of up to 3 stickers in the server to send in the message.
            allowed_mentions: Allowed mentions for the message.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.
            reason: The reason for creating this post

        Returns:
            A GuildForumPost object representing the created post.

        """
        if applied_tags is not MISSING:
            processed = []
            for tag in applied_tags:
                if isinstance(tag, ThreadTag):
                    tag = tag.id
                elif isinstance(tag, (str, int)):
                    tag = self.get_tag(tag, case_insensitive=True)
                    if not tag:
                        continue
                    tag = tag.id
                elif isinstance(tag, dict):
                    tag = tag["id"]
                processed.append(tag)

            applied_tags = processed

        message_payload = models.discord.message.process_message_payload(
            content=content,
            embeds=embeds or embed,
            components=components,
            stickers=stickers,
            allowed_mentions=allowed_mentions,
            tts=tts,
        )

        data = await self._client.http.create_forum_thread(
            self.id,
            name,
            auto_archive_duration,
            message_payload,
            applied_tags,
            rate_limit_per_user,
            files=files or file,
            reason=reason,
        )
        return self._client.cache.place_channel_data(data)

    async def fetch_posts(self) -> List["GuildForumPost"]:
        """
        Requests all active posts within this channel.

        Returns:
            A list of GuildForumPost objects representing the posts.

        """
        # I can guarantee this endpoint will need to be converted to an async iterator eventually
        data = await self._client.http.list_active_threads(self._guild_id)
        threads = [self._client.cache.place_channel_data(post_data) for post_data in data["threads"]]

        return [thread for thread in threads if thread.parent_id == self.id]

    def get_posts(self, *, exclude_archived: bool = True) -> List["GuildForumPost"]:
        """
        List all, cached, active posts within this channel.

        Args:
            exclude_archived: Whether to exclude archived posts from the response

        Returns:
            A list of GuildForumPost objects representing the posts.

        """
        out = [thread for thread in self.guild.threads if thread.parent_id == self.id]
        if exclude_archived:
            return [thread for thread in out if not thread.archived]
        return out

    def archived_posts(self, limit: int = 0, before: Snowflake_Type | None = None) -> ArchivedForumPosts:
        """An async iterator for all archived posts in this channel."""
        return ArchivedForumPosts(self, limit, before)

    async def fetch_post(self, id: "Snowflake_Type", *, force: bool = False) -> "GuildForumPost":
        """
        Fetch a post within this channel.

        Args:
            id: The id of the post to fetch
            force: Whether to force a fetch from the API

        Returns:
            A GuildForumPost object representing the post.

        """
        return await self._client.fetch_channel(id, force=force)

    def get_post(self, id: "Snowflake_Type") -> "GuildForumPost":
        """
        Get a post within this channel.

        Args:
            id: The id of the post to get

        Returns:
            A GuildForumPost object representing the post.

        """
        return self._client.cache.get_channel(id)

    def get_tag(self, value: str | Snowflake_Type, *, case_insensitive: bool = False) -> Optional["ThreadTag"]:
        """
        Get a tag within this channel.

        Args:
            value: The name or ID of the tag to get
            case_insensitive: Whether to ignore case when searching for the tag

        Returns:
            A ThreadTag object representing the tag.

        """
        value = str(value)

        def maybe_insensitive(string: str) -> str:
            return string.lower() if case_insensitive else string

        def predicate(tag: ThreadTag) -> Optional["ThreadTag"]:
            if str(tag.id) == value:
                return tag
            if maybe_insensitive(tag.name) == maybe_insensitive(value):
                return tag

        return next((tag for tag in self.available_tags if predicate(tag)), None)

    async def create_tag(
        self, name: str, emoji: Union["models.PartialEmoji", dict, str, None] = None, moderated: bool = False
    ) -> "ThreadTag":
        """
        Create a tag for this forum.

        Args:
            name: The name of the tag
            emoji: The emoji to use for the tag
            moderated: whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission

        !!! note
            If the emoji is a custom emoji, it must be from the same guild as the channel.

        Returns:
            The created tag object.

        """
        payload = {"channel_id": self.id, "name": name, "moderated": moderated}

        if emoji:
            if isinstance(emoji, str):
                emoji = PartialEmoji.from_str(emoji)
            elif isinstance(emoji, dict):
                emoji = PartialEmoji.from_dict(emoji)

            if emoji.id:
                payload["emoji_id"] = emoji.id
            else:
                payload["emoji_name"] = emoji.name

        data = await self._client.http.create_tag(**payload)

        channel_data = self._client.cache.place_channel_data(data)
        return next(tag for tag in channel_data.available_tags if tag.name == name)

    async def edit_tag(
        self,
        tag_id: "Snowflake_Type",
        *,
        name: str | None = None,
        emoji: Union["models.PartialEmoji", dict, str, None] = None,
    ) -> "ThreadTag":
        """
        Edit a tag for this forum.

        Args:
            tag_id: The id of the tag to edit
            name: The name for this tag
            emoji: The emoji for this tag

        """
        if isinstance(emoji, str):
            emoji = PartialEmoji.from_str(emoji)
        elif isinstance(emoji, dict):
            emoji = PartialEmoji.from_dict(emoji)

        if emoji.id:
            data = await self._client.http.edit_tag(self.id, tag_id, name, emoji_id=emoji.id)
        else:
            data = await self._client.http.edit_tag(self.id, tag_id, name, emoji_name=emoji.name)

        channel_data = self._client.cache.place_channel_data(data)
        return next(tag for tag in channel_data.available_tags if tag.name == name)

    async def delete_tag(self, tag_id: "Snowflake_Type") -> None:
        """
        Delete a tag for this forum.

        Args:
            tag_id: The ID of the tag to delete

        """
        data = await self._client.http.delete_tag(self.id, tag_id)
        self._client.cache.place_channel_data(data)

available_tags: List[ThreadTag] = attrs.field(repr=False, factory=list) class-attribute

A list of tags available to assign to threads

default_forum_layout: ForumLayoutType = attrs.field(repr=False, default=ForumLayoutType.NOT_SET, converter=ForumLayoutType) class-attribute

The default forum layout view used to display posts in GUILD_FORUM channels. Defaults to 0, which indicates a layout view has not been set by a channel admin

default_reaction_emoji: Optional[DefaultReaction] = attrs.field(repr=False, default=None) class-attribute

The default emoji to react with for posts

default_sort_order: Optional[ForumSortOrder] = attrs.field(repr=False, default=None, converter=ForumSortOrder.converter) class-attribute

the default sort order type used to order posts in GUILD_FORUM channels. Defaults to null, which indicates a preferred sort order hasn't been set by a channel admin

rate_limit_per_user: int = attrs.field(repr=False, default=0) class-attribute

Amount of seconds a user has to wait before sending another message (0-21600)

archived_posts(limit=0, before=None)

An async iterator for all archived posts in this channel.

Source code in interactions/models/discord/channel.py
2544
2545
2546
def archived_posts(self, limit: int = 0, before: Snowflake_Type | None = None) -> ArchivedForumPosts:
    """An async iterator for all archived posts in this channel."""
    return ArchivedForumPosts(self, limit, before)

create_post(name, content, applied_tags=MISSING, *, auto_archive_duration=AutoArchiveDuration.ONE_DAY, rate_limit_per_user=MISSING, embeds=None, embed=None, components=None, stickers=None, allowed_mentions=None, files=None, file=None, tts=False, reason=MISSING) async

Create a post within this channel.

Parameters:

Name Type Description Default
name str

The name of the post

required
content str | None

The text content of this post

required
applied_tags Absent[List[Union[Snowflake_Type, ThreadTag, str]]]

A list of tag ids or tag objects to apply to this post

MISSING
auto_archive_duration AutoArchiveDuration

Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.

AutoArchiveDuration.ONE_DAY
rate_limit_per_user Absent[int]

The time users must wait between sending messages

MISSING
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed Optional[Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
stickers Optional[Union[List[Union[Sticker, Snowflake_Type]], Sticker, Snowflake_Type]]

IDs of up to 3 stickers in the server to send in the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
files Optional[Union[UPLOADABLE_TYPE, List[UPLOADABLE_TYPE]]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
file Optional[UPLOADABLE_TYPE]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False
reason Absent[str]

The reason for creating this post

MISSING

Returns:

Type Description
GuildForumPost

A GuildForumPost object representing the created post.

Source code in interactions/models/discord/channel.py
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
async def create_post(
    self,
    name: str,
    content: str | None,
    applied_tags: Absent[List[Union["Snowflake_Type", "ThreadTag", str]]] = MISSING,
    *,
    auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
    rate_limit_per_user: Absent[int] = MISSING,
    embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
    embed: Optional[Union["Embed", dict]] = None,
    components: Optional[
        Union[
            List[List[Union["BaseComponent", dict]]],
            List[Union["BaseComponent", dict]],
            "BaseComponent",
            dict,
        ]
    ] = None,
    stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
    allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
    files: Optional[Union["UPLOADABLE_TYPE", List["UPLOADABLE_TYPE"]]] = None,
    file: Optional["UPLOADABLE_TYPE"] = None,
    tts: bool = False,
    reason: Absent[str] = MISSING,
) -> "GuildForumPost":
    """
    Create a post within this channel.

    Args:
        name: The name of the post
        content: The text content of this post
        applied_tags: A list of tag ids or tag objects to apply to this post
        auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
        rate_limit_per_user: The time users must wait between sending messages
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        stickers: IDs of up to 3 stickers in the server to send in the message.
        allowed_mentions: Allowed mentions for the message.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.
        reason: The reason for creating this post

    Returns:
        A GuildForumPost object representing the created post.

    """
    if applied_tags is not MISSING:
        processed = []
        for tag in applied_tags:
            if isinstance(tag, ThreadTag):
                tag = tag.id
            elif isinstance(tag, (str, int)):
                tag = self.get_tag(tag, case_insensitive=True)
                if not tag:
                    continue
                tag = tag.id
            elif isinstance(tag, dict):
                tag = tag["id"]
            processed.append(tag)

        applied_tags = processed

    message_payload = models.discord.message.process_message_payload(
        content=content,
        embeds=embeds or embed,
        components=components,
        stickers=stickers,
        allowed_mentions=allowed_mentions,
        tts=tts,
    )

    data = await self._client.http.create_forum_thread(
        self.id,
        name,
        auto_archive_duration,
        message_payload,
        applied_tags,
        rate_limit_per_user,
        files=files or file,
        reason=reason,
    )
    return self._client.cache.place_channel_data(data)

create_tag(name, emoji=None, moderated=False) async

Create a tag for this forum.

Parameters:

Name Type Description Default
name str

The name of the tag

required
emoji Union[PartialEmoji, dict, str, None]

The emoji to use for the tag

None
moderated bool

whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission

False

Note

If the emoji is a custom emoji, it must be from the same guild as the channel.

Returns:

Type Description
ThreadTag

The created tag object.

Source code in interactions/models/discord/channel.py
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
async def create_tag(
    self, name: str, emoji: Union["models.PartialEmoji", dict, str, None] = None, moderated: bool = False
) -> "ThreadTag":
    """
    Create a tag for this forum.

    Args:
        name: The name of the tag
        emoji: The emoji to use for the tag
        moderated: whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission

    !!! note
        If the emoji is a custom emoji, it must be from the same guild as the channel.

    Returns:
        The created tag object.

    """
    payload = {"channel_id": self.id, "name": name, "moderated": moderated}

    if emoji:
        if isinstance(emoji, str):
            emoji = PartialEmoji.from_str(emoji)
        elif isinstance(emoji, dict):
            emoji = PartialEmoji.from_dict(emoji)

        if emoji.id:
            payload["emoji_id"] = emoji.id
        else:
            payload["emoji_name"] = emoji.name

    data = await self._client.http.create_tag(**payload)

    channel_data = self._client.cache.place_channel_data(data)
    return next(tag for tag in channel_data.available_tags if tag.name == name)

delete_tag(tag_id) async

Delete a tag for this forum.

Parameters:

Name Type Description Default
tag_id Snowflake_Type

The ID of the tag to delete

required
Source code in interactions/models/discord/channel.py
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
async def delete_tag(self, tag_id: "Snowflake_Type") -> None:
    """
    Delete a tag for this forum.

    Args:
        tag_id: The ID of the tag to delete

    """
    data = await self._client.http.delete_tag(self.id, tag_id)
    self._client.cache.place_channel_data(data)

edit_tag(tag_id, *, name=None, emoji=None) async

Edit a tag for this forum.

Parameters:

Name Type Description Default
tag_id Snowflake_Type

The id of the tag to edit

required
name str | None

The name for this tag

None
emoji Union[PartialEmoji, dict, str, None]

The emoji for this tag

None
Source code in interactions/models/discord/channel.py
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
async def edit_tag(
    self,
    tag_id: "Snowflake_Type",
    *,
    name: str | None = None,
    emoji: Union["models.PartialEmoji", dict, str, None] = None,
) -> "ThreadTag":
    """
    Edit a tag for this forum.

    Args:
        tag_id: The id of the tag to edit
        name: The name for this tag
        emoji: The emoji for this tag

    """
    if isinstance(emoji, str):
        emoji = PartialEmoji.from_str(emoji)
    elif isinstance(emoji, dict):
        emoji = PartialEmoji.from_dict(emoji)

    if emoji.id:
        data = await self._client.http.edit_tag(self.id, tag_id, name, emoji_id=emoji.id)
    else:
        data = await self._client.http.edit_tag(self.id, tag_id, name, emoji_name=emoji.name)

    channel_data = self._client.cache.place_channel_data(data)
    return next(tag for tag in channel_data.available_tags if tag.name == name)

fetch_post(id, *, force=False) async

Fetch a post within this channel.

Parameters:

Name Type Description Default
id Snowflake_Type

The id of the post to fetch

required
force bool

Whether to force a fetch from the API

False

Returns:

Type Description
GuildForumPost

A GuildForumPost object representing the post.

Source code in interactions/models/discord/channel.py
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
async def fetch_post(self, id: "Snowflake_Type", *, force: bool = False) -> "GuildForumPost":
    """
    Fetch a post within this channel.

    Args:
        id: The id of the post to fetch
        force: Whether to force a fetch from the API

    Returns:
        A GuildForumPost object representing the post.

    """
    return await self._client.fetch_channel(id, force=force)

fetch_posts() async

Requests all active posts within this channel.

Returns:

Type Description
List[GuildForumPost]

A list of GuildForumPost objects representing the posts.

Source code in interactions/models/discord/channel.py
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
async def fetch_posts(self) -> List["GuildForumPost"]:
    """
    Requests all active posts within this channel.

    Returns:
        A list of GuildForumPost objects representing the posts.

    """
    # I can guarantee this endpoint will need to be converted to an async iterator eventually
    data = await self._client.http.list_active_threads(self._guild_id)
    threads = [self._client.cache.place_channel_data(post_data) for post_data in data["threads"]]

    return [thread for thread in threads if thread.parent_id == self.id]

get_post(id)

Get a post within this channel.

Parameters:

Name Type Description Default
id Snowflake_Type

The id of the post to get

required

Returns:

Type Description
GuildForumPost

A GuildForumPost object representing the post.

Source code in interactions/models/discord/channel.py
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
def get_post(self, id: "Snowflake_Type") -> "GuildForumPost":
    """
    Get a post within this channel.

    Args:
        id: The id of the post to get

    Returns:
        A GuildForumPost object representing the post.

    """
    return self._client.cache.get_channel(id)

get_posts(*, exclude_archived=True)

List all, cached, active posts within this channel.

Parameters:

Name Type Description Default
exclude_archived bool

Whether to exclude archived posts from the response

True

Returns:

Type Description
List[GuildForumPost]

A list of GuildForumPost objects representing the posts.

Source code in interactions/models/discord/channel.py
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
def get_posts(self, *, exclude_archived: bool = True) -> List["GuildForumPost"]:
    """
    List all, cached, active posts within this channel.

    Args:
        exclude_archived: Whether to exclude archived posts from the response

    Returns:
        A list of GuildForumPost objects representing the posts.

    """
    out = [thread for thread in self.guild.threads if thread.parent_id == self.id]
    if exclude_archived:
        return [thread for thread in out if not thread.archived]
    return out

get_tag(value, *, case_insensitive=False)

Get a tag within this channel.

Parameters:

Name Type Description Default
value str | Snowflake_Type

The name or ID of the tag to get

required
case_insensitive bool

Whether to ignore case when searching for the tag

False

Returns:

Type Description
Optional[ThreadTag]

A ThreadTag object representing the tag.

Source code in interactions/models/discord/channel.py
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
def get_tag(self, value: str | Snowflake_Type, *, case_insensitive: bool = False) -> Optional["ThreadTag"]:
    """
    Get a tag within this channel.

    Args:
        value: The name or ID of the tag to get
        case_insensitive: Whether to ignore case when searching for the tag

    Returns:
        A ThreadTag object representing the tag.

    """
    value = str(value)

    def maybe_insensitive(string: str) -> str:
        return string.lower() if case_insensitive else string

    def predicate(tag: ThreadTag) -> Optional["ThreadTag"]:
        if str(tag.id) == value:
            return tag
        if maybe_insensitive(tag.name) == maybe_insensitive(value):
            return tag

    return next((tag for tag in self.available_tags if predicate(tag)), None)

GuildForumPost

Bases: GuildPublicThread

A forum post

Note

This model is an abstraction of the api - In reality all posts are GuildPublicThread

Source code in interactions/models/discord/channel.py
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class GuildForumPost(GuildPublicThread):
    """
    A forum post

    !!! note
        This model is an abstraction of the api - In reality all posts are GuildPublicThread
    """

    _applied_tags: list[Snowflake_Type] = attrs.field(repr=False, factory=list)

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data = super()._process_dict(data, client)
        data["_applied_tags"] = data.pop("applied_tags") if "applied_tags" in data else []
        return data

    async def edit(
        self,
        *,
        name: Absent[str] = MISSING,
        archived: Absent[bool] = MISSING,
        auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
        applied_tags: Absent[List[Union[Snowflake_Type, ThreadTag]]] = MISSING,
        locked: Absent[bool] = MISSING,
        rate_limit_per_user: Absent[int] = MISSING,
        flags: Absent[Union[int, ChannelFlags]] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "GuildForumPost":
        """
        Edit this thread.

        Args:
            name: 1-100 character channel name
            archived: whether the thread is archived
            applied_tags: list of tags to apply
            auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
            locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
            rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
            flags: channel flags to apply
            reason: The reason for this change

        Returns:
            The edited thread channel object.

        """
        if applied_tags != MISSING:
            applied_tags = [str(tag.id) if isinstance(tag, ThreadTag) else str(tag) for tag in applied_tags]

        return await super().edit(
            name=name,
            archived=archived,
            auto_archive_duration=auto_archive_duration,
            applied_tags=applied_tags,
            locked=locked,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
            flags=flags,
            **kwargs,
        )

    @property
    def applied_tags(self) -> list[ThreadTag]:
        """The tags applied to this thread."""
        if not isinstance(self.parent_channel, GuildForum):
            raise AttributeError("This is only available on forum threads.")
        return [tag for tag in self.parent_channel.available_tags if str(tag.id) in self._applied_tags]

    @property
    def initial_post(self) -> Optional["Message"]:
        """The initial message posted by the OP."""
        if not isinstance(self.parent_channel, GuildForum):
            raise AttributeError("This is only available on forum threads.")
        return self.get_message(self.id)

    @property
    def pinned(self) -> bool:
        """Whether this thread is pinned."""
        return ChannelFlags.PINNED in self.flags

    async def pin(self, reason: Absent[str] = MISSING) -> None:
        """
        Pin this thread.

        Args:
            reason: The reason for this pin

        """
        flags = self.flags | ChannelFlags.PINNED
        await self.edit(flags=flags, reason=reason)

    async def unpin(self, reason: Absent[str] = MISSING) -> None:
        """
        Unpin this thread.

        Args:
            reason: The reason for this unpin

        """
        flags = self.flags & ~ChannelFlags.PINNED
        await self.edit(flags=flags, reason=reason)

applied_tags: list[ThreadTag] property

The tags applied to this thread.

initial_post: Optional[Message] property

The initial message posted by the OP.

pinned: bool property

Whether this thread is pinned.

edit(*, name=MISSING, archived=MISSING, auto_archive_duration=MISSING, applied_tags=MISSING, locked=MISSING, rate_limit_per_user=MISSING, flags=MISSING, reason=MISSING, **kwargs) async

Edit this thread.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
archived Absent[bool]

whether the thread is archived

MISSING
applied_tags Absent[List[Union[Snowflake_Type, ThreadTag]]]

list of tags to apply

MISSING
auto_archive_duration Absent[AutoArchiveDuration]

duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

MISSING
locked Absent[bool]

whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it

MISSING
rate_limit_per_user Absent[int]

amount of seconds a user has to wait before sending another message (0-21600)

MISSING
flags Absent[Union[int, ChannelFlags]]

channel flags to apply

MISSING
reason Absent[str]

The reason for this change

MISSING

Returns:

Type Description
GuildForumPost

The edited thread channel object.

Source code in interactions/models/discord/channel.py
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
async def edit(
    self,
    *,
    name: Absent[str] = MISSING,
    archived: Absent[bool] = MISSING,
    auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
    applied_tags: Absent[List[Union[Snowflake_Type, ThreadTag]]] = MISSING,
    locked: Absent[bool] = MISSING,
    rate_limit_per_user: Absent[int] = MISSING,
    flags: Absent[Union[int, ChannelFlags]] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "GuildForumPost":
    """
    Edit this thread.

    Args:
        name: 1-100 character channel name
        archived: whether the thread is archived
        applied_tags: list of tags to apply
        auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
        locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
        rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
        flags: channel flags to apply
        reason: The reason for this change

    Returns:
        The edited thread channel object.

    """
    if applied_tags != MISSING:
        applied_tags = [str(tag.id) if isinstance(tag, ThreadTag) else str(tag) for tag in applied_tags]

    return await super().edit(
        name=name,
        archived=archived,
        auto_archive_duration=auto_archive_duration,
        applied_tags=applied_tags,
        locked=locked,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
        flags=flags,
        **kwargs,
    )

pin(reason=MISSING) async

Pin this thread.

Parameters:

Name Type Description Default
reason Absent[str]

The reason for this pin

MISSING
Source code in interactions/models/discord/channel.py
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
async def pin(self, reason: Absent[str] = MISSING) -> None:
    """
    Pin this thread.

    Args:
        reason: The reason for this pin

    """
    flags = self.flags | ChannelFlags.PINNED
    await self.edit(flags=flags, reason=reason)

unpin(reason=MISSING) async

Unpin this thread.

Parameters:

Name Type Description Default
reason Absent[str]

The reason for this unpin

MISSING
Source code in interactions/models/discord/channel.py
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
async def unpin(self, reason: Absent[str] = MISSING) -> None:
    """
    Unpin this thread.

    Args:
        reason: The reason for this unpin

    """
    flags = self.flags & ~ChannelFlags.PINNED
    await self.edit(flags=flags, reason=reason)

GuildNews

Bases: GuildChannel, MessageableMixin, InvitableMixin, ThreadableMixin, WebhookMixin

Source code in interactions/models/discord/channel.py
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class GuildNews(GuildChannel, MessageableMixin, InvitableMixin, ThreadableMixin, WebhookMixin):
    topic: Optional[str] = attrs.field(repr=False, default=None)
    """The channel topic (0-1024 characters)"""

    async def edit(
        self,
        *,
        name: Absent[str] = MISSING,
        position: Absent[int] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        parent_id: Absent[Snowflake_Type] = MISSING,
        nsfw: Absent[bool] = MISSING,
        topic: Absent[str] = MISSING,
        channel_type: Absent["ChannelType"] = MISSING,
        default_auto_archive_duration: Absent["AutoArchiveDuration"] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> Union["GuildNews", "GuildText"]:
        """
        Edit the guild text channel.

        Args:
            name: 1-100 character channel name
            position: the position of the channel in the left-hand listing
            permission_overwrites: a list of PermissionOverwrite
            parent_id:  the parent category `Snowflake_Type` for the channel
            nsfw: whether the channel is nsfw
            topic: 0-1024 character channel topic
            channel_type: the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature
            default_auto_archive_duration: optional AutoArchiveDuration
            reason: An optional reason for the audit log

        Returns:
            The edited channel.

        """
        return await super().edit(
            name=name,
            position=position,
            permission_overwrites=permission_overwrites,
            parent_id=parent_id,
            nsfw=nsfw,
            topic=topic,
            type=channel_type,
            default_auto_archive_duration=default_auto_archive_duration,
            reason=reason,
            **kwargs,
        )

    async def follow(self, webhook_channel_id: Snowflake_Type) -> None:
        """
        Follow this channel.

        Args:
            webhook_channel_id: The ID of the channel to post messages from this channel to

        """
        await self._client.http.follow_news_channel(self.id, webhook_channel_id)

    async def create_thread_from_message(
        self,
        name: str,
        message: Snowflake_Type,
        auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
        reason: Absent[str] | None = None,
    ) -> "GuildNewsThread":
        """
        Creates a new news thread in this channel.

        Args:
            name: 1-100 character thread name.
            message: The message to connect this thread to.
            auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
            reason: The reason for creating this thread.

        Returns:
            The created public thread, if successful

        """
        return await self.create_thread(
            name=name,
            message=message,
            auto_archive_duration=auto_archive_duration,
            reason=reason,
        )

topic: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The channel topic (0-1024 characters)

create_thread_from_message(name, message, auto_archive_duration=AutoArchiveDuration.ONE_DAY, reason=None) async

Creates a new news thread in this channel.

Parameters:

Name Type Description Default
name str

1-100 character thread name.

required
message Snowflake_Type

The message to connect this thread to.

required
auto_archive_duration AutoArchiveDuration

Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.

AutoArchiveDuration.ONE_DAY
reason Absent[str] | None

The reason for creating this thread.

None

Returns:

Type Description
GuildNewsThread

The created public thread, if successful

Source code in interactions/models/discord/channel.py
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
async def create_thread_from_message(
    self,
    name: str,
    message: Snowflake_Type,
    auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
    reason: Absent[str] | None = None,
) -> "GuildNewsThread":
    """
    Creates a new news thread in this channel.

    Args:
        name: 1-100 character thread name.
        message: The message to connect this thread to.
        auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
        reason: The reason for creating this thread.

    Returns:
        The created public thread, if successful

    """
    return await self.create_thread(
        name=name,
        message=message,
        auto_archive_duration=auto_archive_duration,
        reason=reason,
    )

edit(*, name=MISSING, position=MISSING, permission_overwrites=MISSING, parent_id=MISSING, nsfw=MISSING, topic=MISSING, channel_type=MISSING, default_auto_archive_duration=MISSING, reason=MISSING, **kwargs) async

Edit the guild text channel.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
position Absent[int]

the position of the channel in the left-hand listing

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

a list of PermissionOverwrite

MISSING
parent_id Absent[Snowflake_Type]

the parent category Snowflake_Type for the channel

MISSING
nsfw Absent[bool]

whether the channel is nsfw

MISSING
topic Absent[str]

0-1024 character channel topic

MISSING
channel_type Absent[ChannelType]

the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature

MISSING
default_auto_archive_duration Absent[AutoArchiveDuration]

optional AutoArchiveDuration

MISSING
reason Absent[str]

An optional reason for the audit log

MISSING

Returns:

Type Description
Union[GuildNews, GuildText]

The edited channel.

Source code in interactions/models/discord/channel.py
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
async def edit(
    self,
    *,
    name: Absent[str] = MISSING,
    position: Absent[int] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    parent_id: Absent[Snowflake_Type] = MISSING,
    nsfw: Absent[bool] = MISSING,
    topic: Absent[str] = MISSING,
    channel_type: Absent["ChannelType"] = MISSING,
    default_auto_archive_duration: Absent["AutoArchiveDuration"] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> Union["GuildNews", "GuildText"]:
    """
    Edit the guild text channel.

    Args:
        name: 1-100 character channel name
        position: the position of the channel in the left-hand listing
        permission_overwrites: a list of PermissionOverwrite
        parent_id:  the parent category `Snowflake_Type` for the channel
        nsfw: whether the channel is nsfw
        topic: 0-1024 character channel topic
        channel_type: the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature
        default_auto_archive_duration: optional AutoArchiveDuration
        reason: An optional reason for the audit log

    Returns:
        The edited channel.

    """
    return await super().edit(
        name=name,
        position=position,
        permission_overwrites=permission_overwrites,
        parent_id=parent_id,
        nsfw=nsfw,
        topic=topic,
        type=channel_type,
        default_auto_archive_duration=default_auto_archive_duration,
        reason=reason,
        **kwargs,
    )

follow(webhook_channel_id) async

Follow this channel.

Parameters:

Name Type Description Default
webhook_channel_id Snowflake_Type

The ID of the channel to post messages from this channel to

required
Source code in interactions/models/discord/channel.py
1663
1664
1665
1666
1667
1668
1669
1670
1671
async def follow(self, webhook_channel_id: Snowflake_Type) -> None:
    """
    Follow this channel.

    Args:
        webhook_channel_id: The ID of the channel to post messages from this channel to

    """
    await self._client.http.follow_news_channel(self.id, webhook_channel_id)

GuildNewsThread

Bases: ThreadChannel

Source code in interactions/models/discord/channel.py
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class GuildNewsThread(ThreadChannel):
    async def edit(
        self,
        *,
        name: Absent[str] = MISSING,
        archived: Absent[bool] = MISSING,
        auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
        locked: Absent[bool] = MISSING,
        rate_limit_per_user: Absent[int] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "GuildNewsThread":
        """
        Edit this thread.

        Args:
            name: 1-100 character channel name
            archived: whether the thread is archived
            auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
            locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
            rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
            reason: The reason for this change

        Returns:
            The edited thread channel object.

        """
        return await super().edit(
            name=name,
            archived=archived,
            auto_archive_duration=auto_archive_duration,
            locked=locked,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
            **kwargs,
        )

edit(*, name=MISSING, archived=MISSING, auto_archive_duration=MISSING, locked=MISSING, rate_limit_per_user=MISSING, reason=MISSING, **kwargs) async

Edit this thread.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
archived Absent[bool]

whether the thread is archived

MISSING
auto_archive_duration Absent[AutoArchiveDuration]

duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

MISSING
locked Absent[bool]

whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it

MISSING
rate_limit_per_user Absent[int]

amount of seconds a user has to wait before sending another message (0-21600)

MISSING
reason Absent[str]

The reason for this change

MISSING

Returns:

Type Description
GuildNewsThread

The edited thread channel object.

Source code in interactions/models/discord/channel.py
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
async def edit(
    self,
    *,
    name: Absent[str] = MISSING,
    archived: Absent[bool] = MISSING,
    auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
    locked: Absent[bool] = MISSING,
    rate_limit_per_user: Absent[int] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "GuildNewsThread":
    """
    Edit this thread.

    Args:
        name: 1-100 character channel name
        archived: whether the thread is archived
        auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
        locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
        rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
        reason: The reason for this change

    Returns:
        The edited thread channel object.

    """
    return await super().edit(
        name=name,
        archived=archived,
        auto_archive_duration=auto_archive_duration,
        locked=locked,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
        **kwargs,
    )

GuildPrivateThread

Bases: ThreadChannel

Source code in interactions/models/discord/channel.py
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class GuildPrivateThread(ThreadChannel):
    invitable: bool = attrs.field(repr=False, default=False)
    """Whether non-moderators can add other non-moderators to a thread"""

    async def edit(
        self,
        *,
        name: Absent[str] = MISSING,
        archived: Absent[bool] = MISSING,
        auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
        locked: Absent[bool] = MISSING,
        rate_limit_per_user: Absent[int] = MISSING,
        invitable: Absent[bool] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "GuildPrivateThread":
        """
        Edit this thread.

        Args:
            name: 1-100 character channel name
            archived: whether the thread is archived
            auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
            locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
            rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
            invitable: whether non-moderators can add other non-moderators to a thread; only available on private threads
            reason: The reason for this change

        Returns:
            The edited thread channel object.

        """
        return await super().edit(
            name=name,
            archived=archived,
            auto_archive_duration=auto_archive_duration,
            locked=locked,
            rate_limit_per_user=rate_limit_per_user,
            invitable=invitable,
            reason=reason,
            **kwargs,
        )

invitable: bool = attrs.field(repr=False, default=False) class-attribute

Whether non-moderators can add other non-moderators to a thread

edit(*, name=MISSING, archived=MISSING, auto_archive_duration=MISSING, locked=MISSING, rate_limit_per_user=MISSING, invitable=MISSING, reason=MISSING, **kwargs) async

Edit this thread.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
archived Absent[bool]

whether the thread is archived

MISSING
auto_archive_duration Absent[AutoArchiveDuration]

duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

MISSING
locked Absent[bool]

whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it

MISSING
rate_limit_per_user Absent[int]

amount of seconds a user has to wait before sending another message (0-21600)

MISSING
invitable Absent[bool]

whether non-moderators can add other non-moderators to a thread; only available on private threads

MISSING
reason Absent[str]

The reason for this change

MISSING

Returns:

Type Description
GuildPrivateThread

The edited thread channel object.

Source code in interactions/models/discord/channel.py
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
async def edit(
    self,
    *,
    name: Absent[str] = MISSING,
    archived: Absent[bool] = MISSING,
    auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
    locked: Absent[bool] = MISSING,
    rate_limit_per_user: Absent[int] = MISSING,
    invitable: Absent[bool] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "GuildPrivateThread":
    """
    Edit this thread.

    Args:
        name: 1-100 character channel name
        archived: whether the thread is archived
        auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
        locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
        rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
        invitable: whether non-moderators can add other non-moderators to a thread; only available on private threads
        reason: The reason for this change

    Returns:
        The edited thread channel object.

    """
    return await super().edit(
        name=name,
        archived=archived,
        auto_archive_duration=auto_archive_duration,
        locked=locked,
        rate_limit_per_user=rate_limit_per_user,
        invitable=invitable,
        reason=reason,
        **kwargs,
    )

GuildPublicThread

Bases: ThreadChannel

Source code in interactions/models/discord/channel.py
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class GuildPublicThread(ThreadChannel):
    async def edit(
        self,
        *,
        name: Absent[str] = MISSING,
        archived: Absent[bool] = MISSING,
        auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
        locked: Absent[bool] = MISSING,
        rate_limit_per_user: Absent[int] = MISSING,
        flags: Absent[Union[int, ChannelFlags]] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "GuildPublicThread":
        """
        Edit this thread.

        Args:
            name: 1-100 character channel name
            archived: whether the thread is archived
            auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
            locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
            rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
            flags: channel flags for forum threads
            reason: The reason for this change

        Returns:
            The edited thread channel object.

        """
        return await super().edit(
            name=name,
            archived=archived,
            auto_archive_duration=auto_archive_duration,
            locked=locked,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
            flags=flags,
            **kwargs,
        )

edit(*, name=MISSING, archived=MISSING, auto_archive_duration=MISSING, locked=MISSING, rate_limit_per_user=MISSING, flags=MISSING, reason=MISSING, **kwargs) async

Edit this thread.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
archived Absent[bool]

whether the thread is archived

MISSING
auto_archive_duration Absent[AutoArchiveDuration]

duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

MISSING
locked Absent[bool]

whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it

MISSING
rate_limit_per_user Absent[int]

amount of seconds a user has to wait before sending another message (0-21600)

MISSING
flags Absent[Union[int, ChannelFlags]]

channel flags for forum threads

MISSING
reason Absent[str]

The reason for this change

MISSING

Returns:

Type Description
GuildPublicThread

The edited thread channel object.

Source code in interactions/models/discord/channel.py
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
async def edit(
    self,
    *,
    name: Absent[str] = MISSING,
    archived: Absent[bool] = MISSING,
    auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
    locked: Absent[bool] = MISSING,
    rate_limit_per_user: Absent[int] = MISSING,
    flags: Absent[Union[int, ChannelFlags]] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "GuildPublicThread":
    """
    Edit this thread.

    Args:
        name: 1-100 character channel name
        archived: whether the thread is archived
        auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
        locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
        rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
        flags: channel flags for forum threads
        reason: The reason for this change

    Returns:
        The edited thread channel object.

    """
    return await super().edit(
        name=name,
        archived=archived,
        auto_archive_duration=auto_archive_duration,
        locked=locked,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
        flags=flags,
        **kwargs,
    )

GuildStageVoice

Bases: GuildVoice

Source code in interactions/models/discord/channel.py
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class GuildStageVoice(GuildVoice):
    stage_instance: "models.StageInstance" = attrs.field(repr=False, default=MISSING)
    """The stage instance that this voice channel belongs to"""

    # todo: Listeners and speakers properties (needs voice state caching)

    async def fetch_stage_instance(self) -> "models.StageInstance":
        """
        Fetches the stage instance associated with this channel.

        Returns:
            The stage instance associated with this channel. If no stage is live, will return None.

        """
        self.stage_instance = models.StageInstance.from_dict(
            await self._client.http.get_stage_instance(self.id), self._client
        )
        return self.stage_instance

    async def create_stage_instance(
        self,
        topic: str,
        privacy_level: StagePrivacyLevel = StagePrivacyLevel.GUILD_ONLY,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.StageInstance":
        """
        Create a stage instance in this channel.

        Args:
            topic: The topic of the stage (1-120 characters)
            privacy_level: The privacy level of the stage
            reason: The reason for creating this instance

        Returns:
            The created stage instance object.

        """
        self.stage_instance = models.StageInstance.from_dict(
            await self._client.http.create_stage_instance(self.id, topic, privacy_level, reason),
            self._client,
        )
        return self.stage_instance

    async def close_stage(self, reason: Absent[Optional[str]] = MISSING) -> None:
        """
        Closes the live stage instance.

        Args:
            reason: The reason for closing the stage

        """
        if not self.stage_instance and not await self.get_stage_instance():
            # we dont know of an active stage instance, so lets check for one
            raise ValueError("No stage instance found")

        await self.stage_instance.delete(reason=reason)

stage_instance: models.StageInstance = attrs.field(repr=False, default=MISSING) class-attribute

The stage instance that this voice channel belongs to

close_stage(reason=MISSING) async

Closes the live stage instance.

Parameters:

Name Type Description Default
reason Absent[Optional[str]]

The reason for closing the stage

MISSING
Source code in interactions/models/discord/channel.py
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
async def close_stage(self, reason: Absent[Optional[str]] = MISSING) -> None:
    """
    Closes the live stage instance.

    Args:
        reason: The reason for closing the stage

    """
    if not self.stage_instance and not await self.get_stage_instance():
        # we dont know of an active stage instance, so lets check for one
        raise ValueError("No stage instance found")

    await self.stage_instance.delete(reason=reason)

create_stage_instance(topic, privacy_level=StagePrivacyLevel.GUILD_ONLY, reason=MISSING) async

Create a stage instance in this channel.

Parameters:

Name Type Description Default
topic str

The topic of the stage (1-120 characters)

required
privacy_level StagePrivacyLevel

The privacy level of the stage

StagePrivacyLevel.GUILD_ONLY
reason Absent[Optional[str]]

The reason for creating this instance

MISSING

Returns:

Type Description
StageInstance

The created stage instance object.

Source code in interactions/models/discord/channel.py
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
async def create_stage_instance(
    self,
    topic: str,
    privacy_level: StagePrivacyLevel = StagePrivacyLevel.GUILD_ONLY,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.StageInstance":
    """
    Create a stage instance in this channel.

    Args:
        topic: The topic of the stage (1-120 characters)
        privacy_level: The privacy level of the stage
        reason: The reason for creating this instance

    Returns:
        The created stage instance object.

    """
    self.stage_instance = models.StageInstance.from_dict(
        await self._client.http.create_stage_instance(self.id, topic, privacy_level, reason),
        self._client,
    )
    return self.stage_instance

fetch_stage_instance() async

Fetches the stage instance associated with this channel.

Returns:

Type Description
StageInstance

The stage instance associated with this channel. If no stage is live, will return None.

Source code in interactions/models/discord/channel.py
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
async def fetch_stage_instance(self) -> "models.StageInstance":
    """
    Fetches the stage instance associated with this channel.

    Returns:
        The stage instance associated with this channel. If no stage is live, will return None.

    """
    self.stage_instance = models.StageInstance.from_dict(
        await self._client.http.get_stage_instance(self.id), self._client
    )
    return self.stage_instance

GuildText

Bases: GuildChannel, MessageableMixin, InvitableMixin, ThreadableMixin, WebhookMixin

Source code in interactions/models/discord/channel.py
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class GuildText(GuildChannel, MessageableMixin, InvitableMixin, ThreadableMixin, WebhookMixin):
    topic: Optional[str] = attrs.field(repr=False, default=None)
    """The channel topic (0-1024 characters)"""

    async def edit(
        self,
        *,
        name: Absent[str] = MISSING,
        position: Absent[int] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        parent_id: Absent[Snowflake_Type] = MISSING,
        nsfw: Absent[bool] = MISSING,
        topic: Absent[str] = MISSING,
        channel_type: Absent["ChannelType"] = MISSING,
        default_auto_archive_duration: Absent["AutoArchiveDuration"] = MISSING,
        rate_limit_per_user: Absent[int] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> Union["GuildText", "GuildNews"]:
        """
        Edit the guild text channel.

        Args:
            name: 1-100 character channel name
            position: the position of the channel in the left-hand listing
            permission_overwrites: a list of PermissionOverwrite
            parent_id:  the parent category `Snowflake_Type` for the channel
            nsfw: whether the channel is nsfw
            topic: 0-1024 character channel topic
            channel_type: the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature
            default_auto_archive_duration: optional AutoArchiveDuration
            rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
            reason: An optional reason for the audit log

        Returns:
            The edited channel.

        """
        return await super().edit(
            name=name,
            position=position,
            permission_overwrites=permission_overwrites,
            parent_id=parent_id,
            nsfw=nsfw,
            topic=topic,
            type=channel_type,
            default_auto_archive_duration=default_auto_archive_duration,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
            **kwargs,
        )

    async def create_public_thread(
        self,
        name: str,
        auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
        rate_limit_per_user: Absent[int] = MISSING,
        reason: Absent[str] | None = None,
    ) -> "GuildPublicThread":
        """
        Creates a new public thread in this channel.

        Args:
            name: 1-100 character thread name.
            auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
            rate_limit_per_user: The time users must wait between sending messages (0-21600).
            reason: The reason for creating this thread.

        Returns:
            The created public thread, if successful

        """
        return await self.create_thread(
            name=name,
            thread_type=ChannelType.GUILD_PUBLIC_THREAD,
            auto_archive_duration=auto_archive_duration,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
        )

    async def create_private_thread(
        self,
        name: str,
        invitable: Absent[bool] = MISSING,
        auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
        rate_limit_per_user: Absent[int] = MISSING,
        reason: Absent[str] | None = None,
    ) -> "GuildPrivateThread":
        """
        Creates a new private thread in this channel.

        Args:
            name: 1-100 character thread name.
            invitable: Whether non-moderators can add other non-moderators to a thread.
            rate_limit_per_user: The time users must wait between sending messages (0-21600).
            auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
            reason: The reason for creating this thread.

        Returns:
            The created thread, if successful

        """
        return await self.create_thread(
            name=name,
            thread_type=ChannelType.GUILD_PRIVATE_THREAD,
            invitable=invitable,
            rate_limit_per_user=rate_limit_per_user,
            auto_archive_duration=auto_archive_duration,
            reason=reason,
        )

    async def create_thread_from_message(
        self,
        name: str,
        message: Snowflake_Type,
        auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
        reason: Absent[str] | None = None,
    ) -> "GuildPublicThread":
        """
        Creates a new public thread in this channel.

        Args:
            name: 1-100 character thread name.
            message: The message to connect this thread to.
            auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
            reason: The reason for creating this thread.

        Returns:
            The created public thread, if successful

        """
        return await self.create_thread(
            name=name,
            message=message,
            auto_archive_duration=auto_archive_duration,
            reason=reason,
        )

topic: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The channel topic (0-1024 characters)

create_private_thread(name, invitable=MISSING, auto_archive_duration=AutoArchiveDuration.ONE_DAY, rate_limit_per_user=MISSING, reason=None) async

Creates a new private thread in this channel.

Parameters:

Name Type Description Default
name str

1-100 character thread name.

required
invitable Absent[bool]

Whether non-moderators can add other non-moderators to a thread.

MISSING
rate_limit_per_user Absent[int]

The time users must wait between sending messages (0-21600).

MISSING
auto_archive_duration AutoArchiveDuration

Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.

AutoArchiveDuration.ONE_DAY
reason Absent[str] | None

The reason for creating this thread.

None

Returns:

Type Description
GuildPrivateThread

The created thread, if successful

Source code in interactions/models/discord/channel.py
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
async def create_private_thread(
    self,
    name: str,
    invitable: Absent[bool] = MISSING,
    auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
    rate_limit_per_user: Absent[int] = MISSING,
    reason: Absent[str] | None = None,
) -> "GuildPrivateThread":
    """
    Creates a new private thread in this channel.

    Args:
        name: 1-100 character thread name.
        invitable: Whether non-moderators can add other non-moderators to a thread.
        rate_limit_per_user: The time users must wait between sending messages (0-21600).
        auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
        reason: The reason for creating this thread.

    Returns:
        The created thread, if successful

    """
    return await self.create_thread(
        name=name,
        thread_type=ChannelType.GUILD_PRIVATE_THREAD,
        invitable=invitable,
        rate_limit_per_user=rate_limit_per_user,
        auto_archive_duration=auto_archive_duration,
        reason=reason,
    )

create_public_thread(name, auto_archive_duration=AutoArchiveDuration.ONE_DAY, rate_limit_per_user=MISSING, reason=None) async

Creates a new public thread in this channel.

Parameters:

Name Type Description Default
name str

1-100 character thread name.

required
auto_archive_duration AutoArchiveDuration

Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.

AutoArchiveDuration.ONE_DAY
rate_limit_per_user Absent[int]

The time users must wait between sending messages (0-21600).

MISSING
reason Absent[str] | None

The reason for creating this thread.

None

Returns:

Type Description
GuildPublicThread

The created public thread, if successful

Source code in interactions/models/discord/channel.py
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
async def create_public_thread(
    self,
    name: str,
    auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
    rate_limit_per_user: Absent[int] = MISSING,
    reason: Absent[str] | None = None,
) -> "GuildPublicThread":
    """
    Creates a new public thread in this channel.

    Args:
        name: 1-100 character thread name.
        auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
        rate_limit_per_user: The time users must wait between sending messages (0-21600).
        reason: The reason for creating this thread.

    Returns:
        The created public thread, if successful

    """
    return await self.create_thread(
        name=name,
        thread_type=ChannelType.GUILD_PUBLIC_THREAD,
        auto_archive_duration=auto_archive_duration,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
    )

create_thread_from_message(name, message, auto_archive_duration=AutoArchiveDuration.ONE_DAY, reason=None) async

Creates a new public thread in this channel.

Parameters:

Name Type Description Default
name str

1-100 character thread name.

required
message Snowflake_Type

The message to connect this thread to.

required
auto_archive_duration AutoArchiveDuration

Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.

AutoArchiveDuration.ONE_DAY
reason Absent[str] | None

The reason for creating this thread.

None

Returns:

Type Description
GuildPublicThread

The created public thread, if successful

Source code in interactions/models/discord/channel.py
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
async def create_thread_from_message(
    self,
    name: str,
    message: Snowflake_Type,
    auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
    reason: Absent[str] | None = None,
) -> "GuildPublicThread":
    """
    Creates a new public thread in this channel.

    Args:
        name: 1-100 character thread name.
        message: The message to connect this thread to.
        auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
        reason: The reason for creating this thread.

    Returns:
        The created public thread, if successful

    """
    return await self.create_thread(
        name=name,
        message=message,
        auto_archive_duration=auto_archive_duration,
        reason=reason,
    )

edit(*, name=MISSING, position=MISSING, permission_overwrites=MISSING, parent_id=MISSING, nsfw=MISSING, topic=MISSING, channel_type=MISSING, default_auto_archive_duration=MISSING, rate_limit_per_user=MISSING, reason=MISSING, **kwargs) async

Edit the guild text channel.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
position Absent[int]

the position of the channel in the left-hand listing

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

a list of PermissionOverwrite

MISSING
parent_id Absent[Snowflake_Type]

the parent category Snowflake_Type for the channel

MISSING
nsfw Absent[bool]

whether the channel is nsfw

MISSING
topic Absent[str]

0-1024 character channel topic

MISSING
channel_type Absent[ChannelType]

the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature

MISSING
default_auto_archive_duration Absent[AutoArchiveDuration]

optional AutoArchiveDuration

MISSING
rate_limit_per_user Absent[int]

amount of seconds a user has to wait before sending another message (0-21600)

MISSING
reason Absent[str]

An optional reason for the audit log

MISSING

Returns:

Type Description
Union[GuildText, GuildNews]

The edited channel.

Source code in interactions/models/discord/channel.py
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
async def edit(
    self,
    *,
    name: Absent[str] = MISSING,
    position: Absent[int] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    parent_id: Absent[Snowflake_Type] = MISSING,
    nsfw: Absent[bool] = MISSING,
    topic: Absent[str] = MISSING,
    channel_type: Absent["ChannelType"] = MISSING,
    default_auto_archive_duration: Absent["AutoArchiveDuration"] = MISSING,
    rate_limit_per_user: Absent[int] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> Union["GuildText", "GuildNews"]:
    """
    Edit the guild text channel.

    Args:
        name: 1-100 character channel name
        position: the position of the channel in the left-hand listing
        permission_overwrites: a list of PermissionOverwrite
        parent_id:  the parent category `Snowflake_Type` for the channel
        nsfw: whether the channel is nsfw
        topic: 0-1024 character channel topic
        channel_type: the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature
        default_auto_archive_duration: optional AutoArchiveDuration
        rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
        reason: An optional reason for the audit log

    Returns:
        The edited channel.

    """
    return await super().edit(
        name=name,
        position=position,
        permission_overwrites=permission_overwrites,
        parent_id=parent_id,
        nsfw=nsfw,
        topic=topic,
        type=channel_type,
        default_auto_archive_duration=default_auto_archive_duration,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
        **kwargs,
    )

InvitableMixin

Source code in interactions/models/discord/channel.py
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
@attrs.define(eq=False, order=False, hash=False, slots=False, kw_only=True)
class InvitableMixin:
    async def create_invite(
        self,
        max_age: int = 86400,
        max_uses: int = 0,
        temporary: bool = False,
        unique: bool = False,
        target_type: Optional[InviteTargetType] = None,
        target_user: Optional[Union[Snowflake_Type, "models.User"]] = None,
        target_application: Optional[Union[Snowflake_Type, "models.Application"]] = None,
        reason: Optional[str] = None,
    ) -> "models.Invite":
        """
        Creates a new channel invite.

        Args:
            max_age: Max age of invite in seconds, default 86400 (24 hours).
            max_uses: Max uses of invite, default 0.
            temporary: Grants temporary membership, default False.
            unique: Invite is unique, default false.
            target_type: Target type for streams and embedded applications.
            target_user: Target User ID for Stream target type.
            target_application: Target Application ID for Embedded App target type.
            reason: The reason for creating this invite.

        Returns:
            Newly created Invite object.

        """
        if target_type:
            if target_type == InviteTargetType.STREAM and not target_user:
                raise ValueError("Stream target must include target user id.")
            if target_type == InviteTargetType.EMBEDDED_APPLICATION and not target_application:
                raise ValueError("Embedded Application target must include target application id.")

        if target_user and target_application:
            raise ValueError("Invite target must be either User or Embedded Application, not both.")
        if target_user:
            target_user = to_snowflake(target_user)
            target_type = InviteTargetType.STREAM
        if target_application:
            target_application = to_snowflake(target_application)
            target_type = InviteTargetType.EMBEDDED_APPLICATION

        invite_data = await self._client.http.create_channel_invite(
            self.id,
            max_age,
            max_uses,
            temporary,
            unique,
            target_user_id=target_user,
            target_application_id=target_application,
            reason=reason,
        )
        return models.Invite.from_dict(invite_data, self._client)

    async def fetch_invites(self) -> List["models.Invite"]:
        """
        Fetches all invites (with invite metadata) for the channel.

        Returns:
            List of Invite objects.

        """
        invites_data = await self._client.http.get_channel_invites(self.id)
        return models.Invite.from_list(invites_data, self._client)

create_invite(max_age=86400, max_uses=0, temporary=False, unique=False, target_type=None, target_user=None, target_application=None, reason=None) async

Creates a new channel invite.

Parameters:

Name Type Description Default
max_age int

Max age of invite in seconds, default 86400 (24 hours).

86400
max_uses int

Max uses of invite, default 0.

0
temporary bool

Grants temporary membership, default False.

False
unique bool

Invite is unique, default false.

False
target_type Optional[InviteTargetType]

Target type for streams and embedded applications.

None
target_user Optional[Union[Snowflake_Type, User]]

Target User ID for Stream target type.

None
target_application Optional[Union[Snowflake_Type, Application]]

Target Application ID for Embedded App target type.

None
reason Optional[str]

The reason for creating this invite.

None

Returns:

Type Description
Invite

Newly created Invite object.

Source code in interactions/models/discord/channel.py
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
async def create_invite(
    self,
    max_age: int = 86400,
    max_uses: int = 0,
    temporary: bool = False,
    unique: bool = False,
    target_type: Optional[InviteTargetType] = None,
    target_user: Optional[Union[Snowflake_Type, "models.User"]] = None,
    target_application: Optional[Union[Snowflake_Type, "models.Application"]] = None,
    reason: Optional[str] = None,
) -> "models.Invite":
    """
    Creates a new channel invite.

    Args:
        max_age: Max age of invite in seconds, default 86400 (24 hours).
        max_uses: Max uses of invite, default 0.
        temporary: Grants temporary membership, default False.
        unique: Invite is unique, default false.
        target_type: Target type for streams and embedded applications.
        target_user: Target User ID for Stream target type.
        target_application: Target Application ID for Embedded App target type.
        reason: The reason for creating this invite.

    Returns:
        Newly created Invite object.

    """
    if target_type:
        if target_type == InviteTargetType.STREAM and not target_user:
            raise ValueError("Stream target must include target user id.")
        if target_type == InviteTargetType.EMBEDDED_APPLICATION and not target_application:
            raise ValueError("Embedded Application target must include target application id.")

    if target_user and target_application:
        raise ValueError("Invite target must be either User or Embedded Application, not both.")
    if target_user:
        target_user = to_snowflake(target_user)
        target_type = InviteTargetType.STREAM
    if target_application:
        target_application = to_snowflake(target_application)
        target_type = InviteTargetType.EMBEDDED_APPLICATION

    invite_data = await self._client.http.create_channel_invite(
        self.id,
        max_age,
        max_uses,
        temporary,
        unique,
        target_user_id=target_user,
        target_application_id=target_application,
        reason=reason,
    )
    return models.Invite.from_dict(invite_data, self._client)

fetch_invites() async

Fetches all invites (with invite metadata) for the channel.

Returns:

Type Description
List[Invite]

List of Invite objects.

Source code in interactions/models/discord/channel.py
546
547
548
549
550
551
552
553
554
555
async def fetch_invites(self) -> List["models.Invite"]:
    """
    Fetches all invites (with invite metadata) for the channel.

    Returns:
        List of Invite objects.

    """
    invites_data = await self._client.http.get_channel_invites(self.id)
    return models.Invite.from_list(invites_data, self._client)

MessageableMixin

Bases: SendMixin

Source code in interactions/models/discord/channel.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
@attrs.define(eq=False, order=False, hash=False, slots=False, kw_only=True)
class MessageableMixin(SendMixin):
    last_message_id: Optional[Snowflake_Type] = attrs.field(
        repr=False, default=None
    )  # TODO May need to think of dynamically updating this.
    """The id of the last message sent in this channel (may not point to an existing or valid message)"""
    default_auto_archive_duration: int = attrs.field(repr=False, default=AutoArchiveDuration.ONE_DAY)
    """Default duration that the clients (not the API) will use for newly created threads, in minutes, to automatically archive the thread after recent activity"""
    last_pin_timestamp: Optional["models.Timestamp"] = attrs.field(
        repr=False, default=None, converter=optional_c(timestamp_converter)
    )
    """When the last pinned message was pinned. This may be None when a message is not pinned."""
    rate_limit_per_user: int = attrs.field(repr=False, default=0)
    """Amount of seconds a user has to wait before sending another message (0-21600)"""

    async def _send_http_request(
        self, message_payload: Union[dict, "FormData"], files: list["UPLOADABLE_TYPE"] | None = None
    ) -> dict:
        return await self._client.http.create_message(message_payload, self.id, files=files)

    async def fetch_message(self, message_id: Snowflake_Type, *, force: bool = False) -> Optional["models.Message"]:
        """
        Fetch a message from the channel.

        Args:
            message_id: ID of message to retrieve.
            force: Whether to force a fetch from the API.

        Returns:
            The message object fetched. If the message is not found, returns None.

        """
        try:
            return await self._client.cache.fetch_message(self.id, message_id, force=force)
        except NotFound:
            return None

    def get_message(self, message_id: Snowflake_Type) -> "models.Message":
        """
        Get a message from the channel.

        Args:
            message_id: ID of message to retrieve.

        Returns:
            The message object fetched.

        """
        message_id = to_snowflake(message_id)
        message: "models.Message" = self._client.cache.get_message(self.id, message_id)
        return message

    def history(
        self,
        limit: int = 100,
        before: Snowflake_Type = None,
        after: Snowflake_Type = None,
        around: Snowflake_Type = None,
    ) -> ChannelHistory:
        """
        Get an async iterator for the history of this channel.

        Args:
            limit: The maximum number of messages to return (set to 0 for no limit)
            before: get messages before this message ID
            after: get messages after this message ID
            around: get messages "around" this message ID

        ??? Hint "Example Usage:"
            ```python
            async for message in channel.history(limit=0):
                if message.author.id == 174918559539920897:
                    print("Found author's message")
                    # ...
                    break
            ```
            or
            ```python
            history = channel.history(limit=250)
            # Flatten the async iterator into a list
            messages = await history.flatten()
            ```

        Returns:
            ChannelHistory (AsyncIterator)

        """
        return ChannelHistory(self, limit, before, after, around)

    async def fetch_messages(
        self,
        limit: int = 50,
        around: Snowflake_Type = MISSING,
        before: Snowflake_Type = MISSING,
        after: Snowflake_Type = MISSING,
    ) -> List["models.Message"]:
        """
        Fetch multiple messages from the channel.

        Args:
            limit: Max number of messages to return, default `50`, max `100`
            around: Message to get messages around
            before: Message to get messages before
            after: Message to get messages after

        Returns:
            A list of messages fetched.

        """
        if limit > 100:
            raise ValueError("You cannot fetch more than 100 messages at once.")

        if around:
            around = to_snowflake(around)
        elif before:
            before = to_snowflake(before)
        elif after:
            after = to_snowflake(after)

        messages_data = await self._client.http.get_channel_messages(
            self.id, limit, around=around, before=before, after=after
        )
        if isinstance(self, GuildChannel):
            for m in messages_data:
                m["guild_id"] = self._guild_id

        return [self._client.cache.place_message_data(m) for m in messages_data]

    async def fetch_pinned_messages(self) -> List["models.Message"]:
        """
        Fetch pinned messages from the channel.

        Returns:
            A list of messages fetched.

        """
        messages_data = await self._client.http.get_pinned_messages(self.id)
        return [self._client.cache.place_message_data(message_data) for message_data in messages_data]

    async def delete_messages(
        self,
        messages: List[Union[Snowflake_Type, "models.Message"]],
        reason: Absent[Optional[str]] = MISSING,
    ) -> None:
        """
        Bulk delete messages from channel.

        Args:
            messages: List of messages or message IDs to delete.
            reason: The reason for this action. Used for audit logs.

        """
        message_ids = [to_snowflake(message) for message in messages]
        # TODO Add check for min/max and duplicates.

        if len(message_ids) == 1:
            # bulk delete messages will throw a http error if only 1 message is passed
            await self.delete_message(message_ids[0], reason)
        else:
            await self._client.http.bulk_delete_messages(self.id, message_ids, reason)

    async def delete_message(self, message: Union[Snowflake_Type, "models.Message"], reason: str | None = None) -> None:
        """
        Delete a single message from a channel.

        Args:
            message: The message to delete
            reason: The reason for this action

        """
        message = to_snowflake(message)
        await self._client.http.delete_message(self.id, message, reason=reason)

    async def purge(
        self,
        deletion_limit: int = 50,
        search_limit: int = 100,
        predicate: Callable[["models.Message"], bool] = MISSING,
        avoid_loading_msg: bool = True,
        return_messages: bool = False,
        before: Optional[Snowflake_Type] = MISSING,
        after: Optional[Snowflake_Type] = MISSING,
        around: Optional[Snowflake_Type] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> int | List["models.Message"]:
        """
        Bulk delete messages within a channel. If a `predicate` is provided, it will be used to determine which messages to delete, otherwise all messages will be deleted within the `deletion_limit`.

        ??? Hint "Example Usage:"
            ```python
            # this will delete the last 20 messages sent by a user with the given ID
            deleted = await channel.purge(deletion_limit=20, predicate=lambda m: m.author.id == 174918559539920897)
            await channel.send(f"{deleted} messages deleted")
            ```

        Args:
            deletion_limit: The target amount of messages to delete
            search_limit: How many messages to search through
            predicate: A function that returns True or False, and takes a message as an argument
            avoid_loading_msg: Should the bot attempt to avoid deleting its own loading messages (recommended enabled)
            return_messages: Should the bot return the messages that were deleted
            before: Search messages before this ID
            after: Search messages after this ID
            around: Search messages around this ID
            reason: The reason for this deletion

        Returns:
            The total amount of messages deleted

        """
        if not predicate:

            def predicate(m) -> bool:
                return True

        to_delete = []

        # 1209600 14 days ago in seconds, 1420070400000 is used to convert to snowflake
        fourteen_days_ago = int((time.time() - 1209600) * 1000.0 - DISCORD_EPOCH) << 22
        async for message in self.history(limit=search_limit, before=before, after=after, around=around):
            if deletion_limit != 0 and len(to_delete) == deletion_limit:
                break

            if not predicate(message):
                # fails predicate
                continue

            if (
                avoid_loading_msg
                and message._author_id == self._client.user.id
                and MessageFlags.LOADING in message.flags
            ):
                continue

            if message.id < fourteen_days_ago:
                # message is too old to be purged
                continue

            to_delete.append(message)

        out = to_delete.copy()
        while len(to_delete):
            iteration = [to_delete.pop().id for i in range(min(100, len(to_delete)))]
            await self.delete_messages(iteration, reason=reason)
        return out if return_messages else len(out)

    async def trigger_typing(self) -> None:
        """Trigger a typing animation in this channel."""
        await self._client.http.trigger_typing_indicator(self.id)

    @property
    def typing(self) -> Typing:
        """A context manager to send a typing state to a given channel as long as long as the wrapped operation takes."""
        return Typing(self)

default_auto_archive_duration: int = attrs.field(repr=False, default=AutoArchiveDuration.ONE_DAY) class-attribute

Default duration that the clients (not the API) will use for newly created threads, in minutes, to automatically archive the thread after recent activity

last_message_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

The id of the last message sent in this channel (may not point to an existing or valid message)

last_pin_timestamp: Optional[models.Timestamp] = attrs.field(repr=False, default=None, converter=optional_c(timestamp_converter)) class-attribute

When the last pinned message was pinned. This may be None when a message is not pinned.

rate_limit_per_user: int = attrs.field(repr=False, default=0) class-attribute

Amount of seconds a user has to wait before sending another message (0-21600)

typing: Typing property

A context manager to send a typing state to a given channel as long as long as the wrapped operation takes.

delete_message(message, reason=None) async

Delete a single message from a channel.

Parameters:

Name Type Description Default
message Union[Snowflake_Type, Message]

The message to delete

required
reason str | None

The reason for this action

None
Source code in interactions/models/discord/channel.py
394
395
396
397
398
399
400
401
402
403
404
async def delete_message(self, message: Union[Snowflake_Type, "models.Message"], reason: str | None = None) -> None:
    """
    Delete a single message from a channel.

    Args:
        message: The message to delete
        reason: The reason for this action

    """
    message = to_snowflake(message)
    await self._client.http.delete_message(self.id, message, reason=reason)

delete_messages(messages, reason=MISSING) async

Bulk delete messages from channel.

Parameters:

Name Type Description Default
messages List[Union[Snowflake_Type, Message]]

List of messages or message IDs to delete.

required
reason Absent[Optional[str]]

The reason for this action. Used for audit logs.

MISSING
Source code in interactions/models/discord/channel.py
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
async def delete_messages(
    self,
    messages: List[Union[Snowflake_Type, "models.Message"]],
    reason: Absent[Optional[str]] = MISSING,
) -> None:
    """
    Bulk delete messages from channel.

    Args:
        messages: List of messages or message IDs to delete.
        reason: The reason for this action. Used for audit logs.

    """
    message_ids = [to_snowflake(message) for message in messages]
    # TODO Add check for min/max and duplicates.

    if len(message_ids) == 1:
        # bulk delete messages will throw a http error if only 1 message is passed
        await self.delete_message(message_ids[0], reason)
    else:
        await self._client.http.bulk_delete_messages(self.id, message_ids, reason)

fetch_message(message_id, *, force=False) async

Fetch a message from the channel.

Parameters:

Name Type Description Default
message_id Snowflake_Type

ID of message to retrieve.

required
force bool

Whether to force a fetch from the API.

False

Returns:

Type Description
Optional[Message]

The message object fetched. If the message is not found, returns None.

Source code in interactions/models/discord/channel.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
async def fetch_message(self, message_id: Snowflake_Type, *, force: bool = False) -> Optional["models.Message"]:
    """
    Fetch a message from the channel.

    Args:
        message_id: ID of message to retrieve.
        force: Whether to force a fetch from the API.

    Returns:
        The message object fetched. If the message is not found, returns None.

    """
    try:
        return await self._client.cache.fetch_message(self.id, message_id, force=force)
    except NotFound:
        return None

fetch_messages(limit=50, around=MISSING, before=MISSING, after=MISSING) async

Fetch multiple messages from the channel.

Parameters:

Name Type Description Default
limit int

Max number of messages to return, default 50, max 100

50
around Snowflake_Type

Message to get messages around

MISSING
before Snowflake_Type

Message to get messages before

MISSING
after Snowflake_Type

Message to get messages after

MISSING

Returns:

Type Description
List[Message]

A list of messages fetched.

Source code in interactions/models/discord/channel.py
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
async def fetch_messages(
    self,
    limit: int = 50,
    around: Snowflake_Type = MISSING,
    before: Snowflake_Type = MISSING,
    after: Snowflake_Type = MISSING,
) -> List["models.Message"]:
    """
    Fetch multiple messages from the channel.

    Args:
        limit: Max number of messages to return, default `50`, max `100`
        around: Message to get messages around
        before: Message to get messages before
        after: Message to get messages after

    Returns:
        A list of messages fetched.

    """
    if limit > 100:
        raise ValueError("You cannot fetch more than 100 messages at once.")

    if around:
        around = to_snowflake(around)
    elif before:
        before = to_snowflake(before)
    elif after:
        after = to_snowflake(after)

    messages_data = await self._client.http.get_channel_messages(
        self.id, limit, around=around, before=before, after=after
    )
    if isinstance(self, GuildChannel):
        for m in messages_data:
            m["guild_id"] = self._guild_id

    return [self._client.cache.place_message_data(m) for m in messages_data]

fetch_pinned_messages() async

Fetch pinned messages from the channel.

Returns:

Type Description
List[Message]

A list of messages fetched.

Source code in interactions/models/discord/channel.py
361
362
363
364
365
366
367
368
369
370
async def fetch_pinned_messages(self) -> List["models.Message"]:
    """
    Fetch pinned messages from the channel.

    Returns:
        A list of messages fetched.

    """
    messages_data = await self._client.http.get_pinned_messages(self.id)
    return [self._client.cache.place_message_data(message_data) for message_data in messages_data]

get_message(message_id)

Get a message from the channel.

Parameters:

Name Type Description Default
message_id Snowflake_Type

ID of message to retrieve.

required

Returns:

Type Description
Message

The message object fetched.

Source code in interactions/models/discord/channel.py
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def get_message(self, message_id: Snowflake_Type) -> "models.Message":
    """
    Get a message from the channel.

    Args:
        message_id: ID of message to retrieve.

    Returns:
        The message object fetched.

    """
    message_id = to_snowflake(message_id)
    message: "models.Message" = self._client.cache.get_message(self.id, message_id)
    return message

history(limit=100, before=None, after=None, around=None)

Get an async iterator for the history of this channel.

Parameters:

Name Type Description Default
limit int

The maximum number of messages to return (set to 0 for no limit)

100
before Snowflake_Type

get messages before this message ID

None
after Snowflake_Type

get messages after this message ID

None
around Snowflake_Type

get messages "around" this message ID

None
Example Usage:

1
2
3
4
5
async for message in channel.history(limit=0):
    if message.author.id == 174918559539920897:
        print("Found author's message")
        # ...
        break
or
1
2
3
history = channel.history(limit=250)
# Flatten the async iterator into a list
messages = await history.flatten()

Returns:

Type Description
ChannelHistory

ChannelHistory (AsyncIterator)

Source code in interactions/models/discord/channel.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
def history(
    self,
    limit: int = 100,
    before: Snowflake_Type = None,
    after: Snowflake_Type = None,
    around: Snowflake_Type = None,
) -> ChannelHistory:
    """
    Get an async iterator for the history of this channel.

    Args:
        limit: The maximum number of messages to return (set to 0 for no limit)
        before: get messages before this message ID
        after: get messages after this message ID
        around: get messages "around" this message ID

    ??? Hint "Example Usage:"
        ```python
        async for message in channel.history(limit=0):
            if message.author.id == 174918559539920897:
                print("Found author's message")
                # ...
                break
        ```
        or
        ```python
        history = channel.history(limit=250)
        # Flatten the async iterator into a list
        messages = await history.flatten()
        ```

    Returns:
        ChannelHistory (AsyncIterator)

    """
    return ChannelHistory(self, limit, before, after, around)

purge(deletion_limit=50, search_limit=100, predicate=MISSING, avoid_loading_msg=True, return_messages=False, before=MISSING, after=MISSING, around=MISSING, reason=MISSING) async

Bulk delete messages within a channel. If a predicate is provided, it will be used to determine which messages to delete, otherwise all messages will be deleted within the deletion_limit.

Example Usage:
1
2
3
# this will delete the last 20 messages sent by a user with the given ID
deleted = await channel.purge(deletion_limit=20, predicate=lambda m: m.author.id == 174918559539920897)
await channel.send(f"{deleted} messages deleted")

Parameters:

Name Type Description Default
deletion_limit int

The target amount of messages to delete

50
search_limit int

How many messages to search through

100
predicate Callable[[Message], bool]

A function that returns True or False, and takes a message as an argument

MISSING
avoid_loading_msg bool

Should the bot attempt to avoid deleting its own loading messages (recommended enabled)

True
return_messages bool

Should the bot return the messages that were deleted

False
before Optional[Snowflake_Type]

Search messages before this ID

MISSING
after Optional[Snowflake_Type]

Search messages after this ID

MISSING
around Optional[Snowflake_Type]

Search messages around this ID

MISSING
reason Absent[Optional[str]]

The reason for this deletion

MISSING

Returns:

Type Description
int | List[Message]

The total amount of messages deleted

Source code in interactions/models/discord/channel.py
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
async def purge(
    self,
    deletion_limit: int = 50,
    search_limit: int = 100,
    predicate: Callable[["models.Message"], bool] = MISSING,
    avoid_loading_msg: bool = True,
    return_messages: bool = False,
    before: Optional[Snowflake_Type] = MISSING,
    after: Optional[Snowflake_Type] = MISSING,
    around: Optional[Snowflake_Type] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> int | List["models.Message"]:
    """
    Bulk delete messages within a channel. If a `predicate` is provided, it will be used to determine which messages to delete, otherwise all messages will be deleted within the `deletion_limit`.

    ??? Hint "Example Usage:"
        ```python
        # this will delete the last 20 messages sent by a user with the given ID
        deleted = await channel.purge(deletion_limit=20, predicate=lambda m: m.author.id == 174918559539920897)
        await channel.send(f"{deleted} messages deleted")
        ```

    Args:
        deletion_limit: The target amount of messages to delete
        search_limit: How many messages to search through
        predicate: A function that returns True or False, and takes a message as an argument
        avoid_loading_msg: Should the bot attempt to avoid deleting its own loading messages (recommended enabled)
        return_messages: Should the bot return the messages that were deleted
        before: Search messages before this ID
        after: Search messages after this ID
        around: Search messages around this ID
        reason: The reason for this deletion

    Returns:
        The total amount of messages deleted

    """
    if not predicate:

        def predicate(m) -> bool:
            return True

    to_delete = []

    # 1209600 14 days ago in seconds, 1420070400000 is used to convert to snowflake
    fourteen_days_ago = int((time.time() - 1209600) * 1000.0 - DISCORD_EPOCH) << 22
    async for message in self.history(limit=search_limit, before=before, after=after, around=around):
        if deletion_limit != 0 and len(to_delete) == deletion_limit:
            break

        if not predicate(message):
            # fails predicate
            continue

        if (
            avoid_loading_msg
            and message._author_id == self._client.user.id
            and MessageFlags.LOADING in message.flags
        ):
            continue

        if message.id < fourteen_days_ago:
            # message is too old to be purged
            continue

        to_delete.append(message)

    out = to_delete.copy()
    while len(to_delete):
        iteration = [to_delete.pop().id for i in range(min(100, len(to_delete)))]
        await self.delete_messages(iteration, reason=reason)
    return out if return_messages else len(out)

trigger_typing() async

Trigger a typing animation in this channel.

Source code in interactions/models/discord/channel.py
479
480
481
async def trigger_typing(self) -> None:
    """Trigger a typing animation in this channel."""
    await self._client.http.trigger_typing_indicator(self.id)

PermissionOverwrite

Bases: SnowflakeObject, DictSerializationMixin

Channel Permissions Overwrite object.

Note

id here is not an attribute of the overwrite, it is the ID of the overwritten instance

Source code in interactions/models/discord/channel.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class PermissionOverwrite(SnowflakeObject, DictSerializationMixin):
    """
    Channel Permissions Overwrite object.

    !!! note
        `id` here is not an attribute of the overwrite, it is the ID of the overwritten instance

    """

    type: "OverwriteType" = attrs.field(repr=True, converter=OverwriteType)
    """Permission overwrite type (role or member)"""
    allow: Optional["Permissions"] = attrs.field(
        repr=True, converter=optional_c(Permissions), kw_only=True, default=None
    )
    """Permissions to allow"""
    deny: Optional["Permissions"] = attrs.field(
        repr=True, converter=optional_c(Permissions), kw_only=True, default=None
    )
    """Permissions to deny"""

    @classmethod
    def for_target(cls, target_type: Union["models.Role", "models.Member", "models.User"]) -> "PermissionOverwrite":
        """
        Create a PermissionOverwrite for a role or member.

        Args:
            target_type: The type of the target (role or member)

        Returns:
            PermissionOverwrite

        """
        if isinstance(target_type, models.Role):
            return cls(type=OverwriteType.ROLE, id=target_type.id)
        if isinstance(target_type, (models.Member, models.User)):
            return cls(type=OverwriteType.MEMBER, id=target_type.id)
        raise TypeError("target_type must be a Role, Member or User")

    def add_allows(self, *perms: "Permissions") -> None:
        """
        Add permissions to allow.

        Args:
            *perms: Permissions to add

        """
        if not self.allow:
            self.allow = Permissions.NONE
        for perm in perms:
            self.allow |= perm

    def add_denies(self, *perms: "Permissions") -> None:
        """
        Add permissions to deny.

        Args:
            *perms: Permissions to add

        """
        if not self.deny:
            self.deny = Permissions.NONE
        for perm in perms:
            self.deny |= perm

allow: Optional[Permissions] = attrs.field(repr=True, converter=optional_c(Permissions), kw_only=True, default=None) class-attribute

Permissions to allow

deny: Optional[Permissions] = attrs.field(repr=True, converter=optional_c(Permissions), kw_only=True, default=None) class-attribute

Permissions to deny

type: OverwriteType = attrs.field(repr=True, converter=OverwriteType) class-attribute

Permission overwrite type (role or member)

add_allows(*perms)

Add permissions to allow.

Parameters:

Name Type Description Default
*perms Permissions

Permissions to add

()
Source code in interactions/models/discord/channel.py
206
207
208
209
210
211
212
213
214
215
216
217
def add_allows(self, *perms: "Permissions") -> None:
    """
    Add permissions to allow.

    Args:
        *perms: Permissions to add

    """
    if not self.allow:
        self.allow = Permissions.NONE
    for perm in perms:
        self.allow |= perm

add_denies(*perms)

Add permissions to deny.

Parameters:

Name Type Description Default
*perms Permissions

Permissions to add

()
Source code in interactions/models/discord/channel.py
219
220
221
222
223
224
225
226
227
228
229
230
def add_denies(self, *perms: "Permissions") -> None:
    """
    Add permissions to deny.

    Args:
        *perms: Permissions to add

    """
    if not self.deny:
        self.deny = Permissions.NONE
    for perm in perms:
        self.deny |= perm

for_target(target_type) classmethod

Create a PermissionOverwrite for a role or member.

Parameters:

Name Type Description Default
target_type Union[Role, Member, User]

The type of the target (role or member)

required

Returns:

Type Description
PermissionOverwrite

PermissionOverwrite

Source code in interactions/models/discord/channel.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
@classmethod
def for_target(cls, target_type: Union["models.Role", "models.Member", "models.User"]) -> "PermissionOverwrite":
    """
    Create a PermissionOverwrite for a role or member.

    Args:
        target_type: The type of the target (role or member)

    Returns:
        PermissionOverwrite

    """
    if isinstance(target_type, models.Role):
        return cls(type=OverwriteType.ROLE, id=target_type.id)
    if isinstance(target_type, (models.Member, models.User)):
        return cls(type=OverwriteType.MEMBER, id=target_type.id)
    raise TypeError("target_type must be a Role, Member or User")

ThreadChannel

Bases: BaseChannel, MessageableMixin, WebhookMixin

Source code in interactions/models/discord/channel.py
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
@attrs.define(eq=False, order=False, hash=False, slots=False, kw_only=True)
class ThreadChannel(BaseChannel, MessageableMixin, WebhookMixin):
    parent_id: Snowflake_Type = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake))
    """id of the text channel this thread was created"""
    owner_id: Snowflake_Type = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake))
    """id of the creator of the thread"""
    topic: Optional[str] = attrs.field(repr=False, default=None)
    """The thread topic (0-1024 characters)"""
    message_count: int = attrs.field(repr=False, default=0)
    """An approximate count of messages in a thread, stops counting at 50"""
    member_count: int = attrs.field(repr=False, default=0)
    """An approximate count of users in a thread, stops counting at 50"""
    archived: bool = attrs.field(repr=False, default=False)
    """Whether the thread is archived"""
    auto_archive_duration: int = attrs.field(
        repr=False,
        default=attrs.Factory(lambda self: self.default_auto_archive_duration, takes_self=True),
    )
    """Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080"""
    locked: bool = attrs.field(repr=False, default=False)
    """Whether the thread is locked"""
    archive_timestamp: Optional["models.Timestamp"] = attrs.field(
        repr=False, default=None, converter=optional_c(timestamp_converter)
    )
    """Timestamp when the thread's archive status was last changed, used for calculating recent activity"""
    create_timestamp: Optional["models.Timestamp"] = attrs.field(
        repr=False, default=None, converter=optional_c(timestamp_converter)
    )
    """Timestamp when the thread was created"""
    flags: ChannelFlags = attrs.field(repr=False, default=ChannelFlags.NONE, converter=ChannelFlags)
    """Flags for the thread"""

    _guild_id: Snowflake_Type = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake))

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data = super()._process_dict(data, client)
        thread_metadata: dict = data.get("thread_metadata", {})
        data.update(thread_metadata)
        return data

    @property
    def is_private(self) -> bool:
        """Is this a private thread?"""
        return self.type == ChannelType.GUILD_PRIVATE_THREAD

    @property
    def guild(self) -> "models.Guild":
        """The guild this channel belongs to."""
        return self._client.cache.get_guild(self._guild_id)

    @property
    def parent_channel(self) -> Union[GuildText, "GuildForum"]:
        """The channel this thread is a child of."""
        return self._client.cache.get_channel(self.parent_id)

    @property
    def parent_message(self) -> Optional["Message"]:
        """The message this thread is a child of."""
        return self._client.cache.get_message(self.parent_id, self.id)

    @property
    def mention(self) -> str:
        """Returns a string that would mention this thread."""
        return f"<#{self.id}>"

    @property
    def permission_overwrites(self) -> List["PermissionOverwrite"]:
        """The permission overwrites for this channel."""
        return []

    @property
    def clyde_created(self) -> bool:
        """Whether this thread was created by Clyde."""
        return ChannelFlags.CLYDE_THREAD in self.flags

    def permissions_for(self, instance: Snowflake_Type) -> Permissions:
        """
        Calculates permissions for an instance

        Args:
            instance: Member or Role instance (or its ID)

        Returns:
            Permissions data

        Raises:
            ValueError: If could not find any member or role by given ID
            RuntimeError: If given instance is from another guild

        """
        if self.parent_channel:
            return self.parent_channel.permissions_for(instance)
        return Permissions.NONE

    async def fetch_members(self) -> List["models.ThreadMember"]:
        """Get the members that have access to this thread."""
        members_data = await self._client.http.list_thread_members(self.id)
        return models.ThreadMember.from_list(members_data, self._client)

    async def add_member(self, member: Union["models.Member", Snowflake_Type]) -> None:
        """
        Add a member to this thread.

        Args:
            member: The member to add

        """
        await self._client.http.add_thread_member(self.id, to_snowflake(member))

    async def remove_member(self, member: Union["models.Member", Snowflake_Type]) -> None:
        """
        Remove a member from this thread.

        Args:
            member: The member to remove

        """
        await self._client.http.remove_thread_member(self.id, to_snowflake(member))

    async def join(self) -> None:
        """Join this thread."""
        await self._client.http.join_thread(self.id)

    async def leave(self) -> None:
        """Leave this thread."""
        await self._client.http.leave_thread(self.id)

    async def archive(self, locked: bool = False, reason: Absent[str] = MISSING) -> "TYPE_THREAD_CHANNEL":
        """
        Helper method to archive this thread.

        Args:
            locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
            reason: The reason for this archive

        Returns:
            The archived thread channel object.

        """
        return await super().edit(locked=locked, archived=True, reason=reason)

archive_timestamp: Optional[models.Timestamp] = attrs.field(repr=False, default=None, converter=optional_c(timestamp_converter)) class-attribute

Timestamp when the thread's archive status was last changed, used for calculating recent activity

archived: bool = attrs.field(repr=False, default=False) class-attribute

Whether the thread is archived

auto_archive_duration: int = attrs.field(repr=False, default=attrs.Factory(lambda self: self.default_auto_archive_duration, takes_self=True)) class-attribute

Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

clyde_created: bool property

Whether this thread was created by Clyde.

create_timestamp: Optional[models.Timestamp] = attrs.field(repr=False, default=None, converter=optional_c(timestamp_converter)) class-attribute

Timestamp when the thread was created

flags: ChannelFlags = attrs.field(repr=False, default=ChannelFlags.NONE, converter=ChannelFlags) class-attribute

Flags for the thread

guild: models.Guild property

The guild this channel belongs to.

is_private: bool property

Is this a private thread?

locked: bool = attrs.field(repr=False, default=False) class-attribute

Whether the thread is locked

member_count: int = attrs.field(repr=False, default=0) class-attribute

An approximate count of users in a thread, stops counting at 50

mention: str property

Returns a string that would mention this thread.

message_count: int = attrs.field(repr=False, default=0) class-attribute

An approximate count of messages in a thread, stops counting at 50

owner_id: Snowflake_Type = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake)) class-attribute

id of the creator of the thread

parent_channel: Union[GuildText, GuildForum] property

The channel this thread is a child of.

parent_id: Snowflake_Type = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake)) class-attribute

id of the text channel this thread was created

parent_message: Optional[Message] property

The message this thread is a child of.

permission_overwrites: List[PermissionOverwrite] property

The permission overwrites for this channel.

topic: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The thread topic (0-1024 characters)

add_member(member) async

Add a member to this thread.

Parameters:

Name Type Description Default
member Union[Member, Snowflake_Type]

The member to add

required
Source code in interactions/models/discord/channel.py
1947
1948
1949
1950
1951
1952
1953
1954
1955
async def add_member(self, member: Union["models.Member", Snowflake_Type]) -> None:
    """
    Add a member to this thread.

    Args:
        member: The member to add

    """
    await self._client.http.add_thread_member(self.id, to_snowflake(member))

archive(locked=False, reason=MISSING) async

Helper method to archive this thread.

Parameters:

Name Type Description Default
locked bool

whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it

False
reason Absent[str]

The reason for this archive

MISSING

Returns:

Type Description
TYPE_THREAD_CHANNEL

The archived thread channel object.

Source code in interactions/models/discord/channel.py
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
async def archive(self, locked: bool = False, reason: Absent[str] = MISSING) -> "TYPE_THREAD_CHANNEL":
    """
    Helper method to archive this thread.

    Args:
        locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
        reason: The reason for this archive

    Returns:
        The archived thread channel object.

    """
    return await super().edit(locked=locked, archived=True, reason=reason)

fetch_members() async

Get the members that have access to this thread.

Source code in interactions/models/discord/channel.py
1942
1943
1944
1945
async def fetch_members(self) -> List["models.ThreadMember"]:
    """Get the members that have access to this thread."""
    members_data = await self._client.http.list_thread_members(self.id)
    return models.ThreadMember.from_list(members_data, self._client)

join() async

Join this thread.

Source code in interactions/models/discord/channel.py
1967
1968
1969
async def join(self) -> None:
    """Join this thread."""
    await self._client.http.join_thread(self.id)

leave() async

Leave this thread.

Source code in interactions/models/discord/channel.py
1971
1972
1973
async def leave(self) -> None:
    """Leave this thread."""
    await self._client.http.leave_thread(self.id)

permissions_for(instance)

Calculates permissions for an instance

Parameters:

Name Type Description Default
instance Snowflake_Type

Member or Role instance (or its ID)

required

Returns:

Type Description
Permissions

Permissions data

Raises:

Type Description
ValueError

If could not find any member or role by given ID

RuntimeError

If given instance is from another guild

Source code in interactions/models/discord/channel.py
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
def permissions_for(self, instance: Snowflake_Type) -> Permissions:
    """
    Calculates permissions for an instance

    Args:
        instance: Member or Role instance (or its ID)

    Returns:
        Permissions data

    Raises:
        ValueError: If could not find any member or role by given ID
        RuntimeError: If given instance is from another guild

    """
    if self.parent_channel:
        return self.parent_channel.permissions_for(instance)
    return Permissions.NONE

remove_member(member) async

Remove a member from this thread.

Parameters:

Name Type Description Default
member Union[Member, Snowflake_Type]

The member to remove

required
Source code in interactions/models/discord/channel.py
1957
1958
1959
1960
1961
1962
1963
1964
1965
async def remove_member(self, member: Union["models.Member", Snowflake_Type]) -> None:
    """
    Remove a member from this thread.

    Args:
        member: The member to remove

    """
    await self._client.http.remove_thread_member(self.id, to_snowflake(member))

ThreadableMixin

Source code in interactions/models/discord/channel.py
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
@attrs.define(eq=False, order=False, hash=False, slots=False, kw_only=True)
class ThreadableMixin:
    async def create_thread(
        self,
        name: str,
        message: Absent[Snowflake_Type] = MISSING,
        thread_type: Absent[ChannelType] = MISSING,
        invitable: Absent[bool] = MISSING,
        rate_limit_per_user: Absent[int] = MISSING,
        auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
        reason: Absent[str] | None = None,
    ) -> "TYPE_THREAD_CHANNEL":
        """
        Creates a new thread in this channel. If a message is provided, it will be used as the initial message.

        Args:
            name: 1-100 character thread name
            message: The message to connect this thread to. Required for news channel.
            thread_type: Is the thread private or public. Not applicable to news channel, it will always be GUILD_NEWS_THREAD.
            invitable: whether non-moderators can add other non-moderators to a thread. Only applicable when creating a private thread.
            rate_limit_per_user: The time users must wait between sending messages (0-21600).
            auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
            reason: The reason for creating this thread.

        Returns:
            The created thread, if successful

        """
        if self.type == ChannelType.GUILD_NEWS and not message:
            raise ValueError("News channel must include message to create thread from.")

        if message and (thread_type or invitable):
            raise ValueError("Message cannot be used with thread_type or invitable.")

        if thread_type != ChannelType.GUILD_PRIVATE_THREAD and invitable:
            raise ValueError("Invitable only applies to private threads.")

        thread_data = await self._client.http.create_thread(
            channel_id=self.id,
            name=name,
            thread_type=thread_type,
            invitable=invitable,
            rate_limit_per_user=rate_limit_per_user,
            auto_archive_duration=auto_archive_duration,
            message_id=to_optional_snowflake(message),
            reason=reason,
        )
        return self._client.cache.place_channel_data(thread_data)

    async def fetch_public_archived_threads(
        self, limit: int | None = None, before: Optional["models.Timestamp"] = None
    ) -> "models.ThreadList":
        """
        Get a `ThreadList` of archived **public** threads available in this channel.

        Args:
            limit: optional maximum number of threads to return
            before: Returns threads before this timestamp

        Returns:
            A `ThreadList` of archived threads.

        """
        threads_data = await self._client.http.list_public_archived_threads(
            channel_id=self.id, limit=limit, before=before
        )
        threads_data["id"] = self.id
        return models.ThreadList.from_dict(threads_data, self._client)

    async def fetch_private_archived_threads(
        self, limit: int | None = None, before: Optional["models.Timestamp"] = None
    ) -> "models.ThreadList":
        """
        Get a `ThreadList` of archived **private** threads available in this channel.

        Args:
            limit: optional maximum number of threads to return
            before: Returns threads before this timestamp

        Returns:
            A `ThreadList` of archived threads.

        """
        threads_data = await self._client.http.list_private_archived_threads(
            channel_id=self.id, limit=limit, before=before
        )
        threads_data["id"] = self.id
        return models.ThreadList.from_dict(threads_data, self._client)

    async def fetch_archived_threads(
        self, limit: int | None = None, before: Optional["models.Timestamp"] = None
    ) -> "models.ThreadList":
        """
        Get a `ThreadList` of archived threads available in this channel.

        Args:
            limit: optional maximum number of threads to return
            before: Returns threads before this timestamp

        Returns:
            A `ThreadList` of archived threads.

        """
        threads_data = await self._client.http.list_private_archived_threads(
            channel_id=self.id, limit=limit, before=before
        )
        threads_data.update(
            await self._client.http.list_public_archived_threads(channel_id=self.id, limit=limit, before=before)
        )
        threads_data["id"] = self.id
        return models.ThreadList.from_dict(threads_data, self._client)

    async def fetch_joined_private_archived_threads(
        self, limit: int | None = None, before: Optional["models.Timestamp"] = None
    ) -> "models.ThreadList":
        """
        Get a `ThreadList` of threads the bot is a participant of in this channel.

        Args:
            limit: optional maximum number of threads to return
            before: Returns threads before this timestamp

        Returns:
            A `ThreadList` of threads the bot is a participant of.

        """
        threads_data = await self._client.http.list_joined_private_archived_threads(
            channel_id=self.id, limit=limit, before=before
        )
        threads_data["id"] = self.id
        return models.ThreadList.from_dict(threads_data, self._client)

    async def fetch_active_threads(self) -> "models.ThreadList":
        """
        Gets all active threads in the channel, including public and private threads.

        Returns:
            A `ThreadList` of active threads.

        """
        threads_data = await self._client.http.list_active_threads(guild_id=self._guild_id)

        # delete the items where the channel_id does not match
        removed_thread_ids = []
        cleaned_threads_data_threads = []
        for thread in threads_data["threads"]:
            if thread["parent_id"] == str(self.id):
                cleaned_threads_data_threads.append(thread)
            else:
                removed_thread_ids.append(thread["id"])
        threads_data["threads"] = cleaned_threads_data_threads

        cleaned_member_data_threads = [
            thread_member for thread_member in threads_data["members"] if thread_member["id"] not in removed_thread_ids
        ]
        threads_data["members"] = cleaned_member_data_threads

        return models.ThreadList.from_dict(threads_data, self._client)

    async def fetch_all_threads(self) -> "models.ThreadList":
        """
        Gets all threads in the channel. Active and archived, including public and private threads.

        Returns:
            A `ThreadList` of all threads.

        """
        threads = await self.fetch_active_threads()

        # update that data with the archived threads
        archived_threads = await self.fetch_archived_threads()
        threads.threads.extend(archived_threads.threads)
        threads.members.extend(archived_threads.members)

        return threads

create_thread(name, message=MISSING, thread_type=MISSING, invitable=MISSING, rate_limit_per_user=MISSING, auto_archive_duration=AutoArchiveDuration.ONE_DAY, reason=None) async

Creates a new thread in this channel. If a message is provided, it will be used as the initial message.

Parameters:

Name Type Description Default
name str

1-100 character thread name

required
message Absent[Snowflake_Type]

The message to connect this thread to. Required for news channel.

MISSING
thread_type Absent[ChannelType]

Is the thread private or public. Not applicable to news channel, it will always be GUILD_NEWS_THREAD.

MISSING
invitable Absent[bool]

whether non-moderators can add other non-moderators to a thread. Only applicable when creating a private thread.

MISSING
rate_limit_per_user Absent[int]

The time users must wait between sending messages (0-21600).

MISSING
auto_archive_duration AutoArchiveDuration

Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.

AutoArchiveDuration.ONE_DAY
reason Absent[str] | None

The reason for creating this thread.

None

Returns:

Type Description
TYPE_THREAD_CHANNEL

The created thread, if successful

Source code in interactions/models/discord/channel.py
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
async def create_thread(
    self,
    name: str,
    message: Absent[Snowflake_Type] = MISSING,
    thread_type: Absent[ChannelType] = MISSING,
    invitable: Absent[bool] = MISSING,
    rate_limit_per_user: Absent[int] = MISSING,
    auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
    reason: Absent[str] | None = None,
) -> "TYPE_THREAD_CHANNEL":
    """
    Creates a new thread in this channel. If a message is provided, it will be used as the initial message.

    Args:
        name: 1-100 character thread name
        message: The message to connect this thread to. Required for news channel.
        thread_type: Is the thread private or public. Not applicable to news channel, it will always be GUILD_NEWS_THREAD.
        invitable: whether non-moderators can add other non-moderators to a thread. Only applicable when creating a private thread.
        rate_limit_per_user: The time users must wait between sending messages (0-21600).
        auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
        reason: The reason for creating this thread.

    Returns:
        The created thread, if successful

    """
    if self.type == ChannelType.GUILD_NEWS and not message:
        raise ValueError("News channel must include message to create thread from.")

    if message and (thread_type or invitable):
        raise ValueError("Message cannot be used with thread_type or invitable.")

    if thread_type != ChannelType.GUILD_PRIVATE_THREAD and invitable:
        raise ValueError("Invitable only applies to private threads.")

    thread_data = await self._client.http.create_thread(
        channel_id=self.id,
        name=name,
        thread_type=thread_type,
        invitable=invitable,
        rate_limit_per_user=rate_limit_per_user,
        auto_archive_duration=auto_archive_duration,
        message_id=to_optional_snowflake(message),
        reason=reason,
    )
    return self._client.cache.place_channel_data(thread_data)

fetch_active_threads() async

Gets all active threads in the channel, including public and private threads.

Returns:

Type Description
ThreadList

A ThreadList of active threads.

Source code in interactions/models/discord/channel.py
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
async def fetch_active_threads(self) -> "models.ThreadList":
    """
    Gets all active threads in the channel, including public and private threads.

    Returns:
        A `ThreadList` of active threads.

    """
    threads_data = await self._client.http.list_active_threads(guild_id=self._guild_id)

    # delete the items where the channel_id does not match
    removed_thread_ids = []
    cleaned_threads_data_threads = []
    for thread in threads_data["threads"]:
        if thread["parent_id"] == str(self.id):
            cleaned_threads_data_threads.append(thread)
        else:
            removed_thread_ids.append(thread["id"])
    threads_data["threads"] = cleaned_threads_data_threads

    cleaned_member_data_threads = [
        thread_member for thread_member in threads_data["members"] if thread_member["id"] not in removed_thread_ids
    ]
    threads_data["members"] = cleaned_member_data_threads

    return models.ThreadList.from_dict(threads_data, self._client)

fetch_all_threads() async

Gets all threads in the channel. Active and archived, including public and private threads.

Returns:

Type Description
ThreadList

A ThreadList of all threads.

Source code in interactions/models/discord/channel.py
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
async def fetch_all_threads(self) -> "models.ThreadList":
    """
    Gets all threads in the channel. Active and archived, including public and private threads.

    Returns:
        A `ThreadList` of all threads.

    """
    threads = await self.fetch_active_threads()

    # update that data with the archived threads
    archived_threads = await self.fetch_archived_threads()
    threads.threads.extend(archived_threads.threads)
    threads.members.extend(archived_threads.members)

    return threads

fetch_archived_threads(limit=None, before=None) async

Get a ThreadList of archived threads available in this channel.

Parameters:

Name Type Description Default
limit int | None

optional maximum number of threads to return

None
before Optional[Timestamp]

Returns threads before this timestamp

None

Returns:

Type Description
ThreadList

A ThreadList of archived threads.

Source code in interactions/models/discord/channel.py
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
async def fetch_archived_threads(
    self, limit: int | None = None, before: Optional["models.Timestamp"] = None
) -> "models.ThreadList":
    """
    Get a `ThreadList` of archived threads available in this channel.

    Args:
        limit: optional maximum number of threads to return
        before: Returns threads before this timestamp

    Returns:
        A `ThreadList` of archived threads.

    """
    threads_data = await self._client.http.list_private_archived_threads(
        channel_id=self.id, limit=limit, before=before
    )
    threads_data.update(
        await self._client.http.list_public_archived_threads(channel_id=self.id, limit=limit, before=before)
    )
    threads_data["id"] = self.id
    return models.ThreadList.from_dict(threads_data, self._client)

fetch_joined_private_archived_threads(limit=None, before=None) async

Get a ThreadList of threads the bot is a participant of in this channel.

Parameters:

Name Type Description Default
limit int | None

optional maximum number of threads to return

None
before Optional[Timestamp]

Returns threads before this timestamp

None

Returns:

Type Description
ThreadList

A ThreadList of threads the bot is a participant of.

Source code in interactions/models/discord/channel.py
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
async def fetch_joined_private_archived_threads(
    self, limit: int | None = None, before: Optional["models.Timestamp"] = None
) -> "models.ThreadList":
    """
    Get a `ThreadList` of threads the bot is a participant of in this channel.

    Args:
        limit: optional maximum number of threads to return
        before: Returns threads before this timestamp

    Returns:
        A `ThreadList` of threads the bot is a participant of.

    """
    threads_data = await self._client.http.list_joined_private_archived_threads(
        channel_id=self.id, limit=limit, before=before
    )
    threads_data["id"] = self.id
    return models.ThreadList.from_dict(threads_data, self._client)

fetch_private_archived_threads(limit=None, before=None) async

Get a ThreadList of archived private threads available in this channel.

Parameters:

Name Type Description Default
limit int | None

optional maximum number of threads to return

None
before Optional[Timestamp]

Returns threads before this timestamp

None

Returns:

Type Description
ThreadList

A ThreadList of archived threads.

Source code in interactions/models/discord/channel.py
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
async def fetch_private_archived_threads(
    self, limit: int | None = None, before: Optional["models.Timestamp"] = None
) -> "models.ThreadList":
    """
    Get a `ThreadList` of archived **private** threads available in this channel.

    Args:
        limit: optional maximum number of threads to return
        before: Returns threads before this timestamp

    Returns:
        A `ThreadList` of archived threads.

    """
    threads_data = await self._client.http.list_private_archived_threads(
        channel_id=self.id, limit=limit, before=before
    )
    threads_data["id"] = self.id
    return models.ThreadList.from_dict(threads_data, self._client)

fetch_public_archived_threads(limit=None, before=None) async

Get a ThreadList of archived public threads available in this channel.

Parameters:

Name Type Description Default
limit int | None

optional maximum number of threads to return

None
before Optional[Timestamp]

Returns threads before this timestamp

None

Returns:

Type Description
ThreadList

A ThreadList of archived threads.

Source code in interactions/models/discord/channel.py
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
async def fetch_public_archived_threads(
    self, limit: int | None = None, before: Optional["models.Timestamp"] = None
) -> "models.ThreadList":
    """
    Get a `ThreadList` of archived **public** threads available in this channel.

    Args:
        limit: optional maximum number of threads to return
        before: Returns threads before this timestamp

    Returns:
        A `ThreadList` of archived threads.

    """
    threads_data = await self._client.http.list_public_archived_threads(
        channel_id=self.id, limit=limit, before=before
    )
    threads_data["id"] = self.id
    return models.ThreadList.from_dict(threads_data, self._client)

VoiceChannel

Bases: GuildChannel

Source code in interactions/models/discord/channel.py
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
@attrs.define(eq=False, order=False, hash=False, slots=False, kw_only=True)
class VoiceChannel(GuildChannel):  # May not be needed, can be directly just GuildVoice.
    bitrate: int = attrs.field(
        repr=False,
    )
    """The bitrate (in bits) of the voice channel"""
    user_limit: int = attrs.field(
        repr=False,
    )
    """The user limit of the voice channel"""
    rtc_region: str = attrs.field(repr=False, default="auto")
    """Voice region id for the voice channel, automatic when set to None"""
    video_quality_mode: Union[VideoQualityMode, int] = attrs.field(repr=False, default=VideoQualityMode.AUTO)
    """The camera video quality mode of the voice channel, 1 when not present"""
    _voice_member_ids: list[Snowflake_Type] = attrs.field(repr=False, factory=list)

    async def edit(
        self,
        *,
        name: Absent[str] = MISSING,
        position: Absent[int] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        parent_id: Absent[Snowflake_Type] = MISSING,
        bitrate: Absent[int] = MISSING,
        user_limit: Absent[int] = MISSING,
        rtc_region: Absent[str] = MISSING,
        video_quality_mode: Absent[VideoQualityMode] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> Union["GuildVoice", "GuildStageVoice"]:
        """
        Edit guild voice channel.

        Args:
            name: 1-100 character channel name
            position: the position of the channel in the left-hand listing
            permission_overwrites: a list of `PermissionOverwrite` to apply to the channel
            parent_id: the parent category `Snowflake_Type` for the channel
            bitrate: the bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)
            user_limit: the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit
            rtc_region: channel voice region id, automatic when not set
            video_quality_mode: the camera video quality mode of the voice channel
            reason: optional reason for audit logs

        Returns:
            The edited voice channel object.

        """
        return await super().edit(
            name=name,
            position=position,
            permission_overwrites=permission_overwrites,
            parent_id=parent_id,
            bitrate=bitrate,
            user_limit=user_limit,
            rtc_region=rtc_region,
            video_quality_mode=video_quality_mode,
            reason=reason,
            **kwargs,
        )

    @property
    def members(self) -> List["models.Member"]:
        """Returns a list of members that have access to this voice channel"""
        return [m for m in self.guild.members if Permissions.CONNECT in m.channel_permissions(self)]  # type: ignore

    @property
    def voice_members(self) -> List["models.Member"]:
        """
        Returns a list of members that are currently in the channel.

        !!! note
            This will not be accurate if the bot was offline while users joined the channel
        """
        return [self._client.cache.get_member(self._guild_id, member_id) for member_id in self._voice_member_ids]

    @property
    def voice_state(self) -> Optional["ActiveVoiceState"]:
        """Returns the voice state of the bot in this channel if it is connected"""
        return self._client.get_bot_voice_state(self._guild_id)

    async def connect(self, muted: bool = False, deafened: bool = False) -> "ActiveVoiceState":
        """
        Connect the bot to this voice channel, or move the bot to this voice channel if it is already connected in another voice channel.

        Args:
            muted: Whether the bot should be muted when connected.
            deafened: Whether the bot should be deafened when connected.

        Returns:
            The new active voice state on successfully connection.

        """
        if not self.voice_state:
            return await self._client.connect_to_vc(self._guild_id, self.id, muted, deafened)
        await self.voice_state.move(self.id)
        return self.voice_state

    async def disconnect(self) -> None:
        """
        Disconnect from the currently connected voice state.

        Raises:
            VoiceNotConnected: if the bot is not connected to a voice channel

        """
        if self.voice_state:
            return await self.voice_state.disconnect()
        raise VoiceNotConnected

bitrate: int = attrs.field(repr=False) class-attribute

The bitrate (in bits) of the voice channel

members: List[models.Member] property

Returns a list of members that have access to this voice channel

rtc_region: str = attrs.field(repr=False, default='auto') class-attribute

Voice region id for the voice channel, automatic when set to None

user_limit: int = attrs.field(repr=False) class-attribute

The user limit of the voice channel

video_quality_mode: Union[VideoQualityMode, int] = attrs.field(repr=False, default=VideoQualityMode.AUTO) class-attribute

The camera video quality mode of the voice channel, 1 when not present

voice_members: List[models.Member] property

Returns a list of members that are currently in the channel.

Note

This will not be accurate if the bot was offline while users joined the channel

voice_state: Optional[ActiveVoiceState] property

Returns the voice state of the bot in this channel if it is connected

connect(muted=False, deafened=False) async

Connect the bot to this voice channel, or move the bot to this voice channel if it is already connected in another voice channel.

Parameters:

Name Type Description Default
muted bool

Whether the bot should be muted when connected.

False
deafened bool

Whether the bot should be deafened when connected.

False

Returns:

Type Description
ActiveVoiceState

The new active voice state on successfully connection.

Source code in interactions/models/discord/channel.py
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
async def connect(self, muted: bool = False, deafened: bool = False) -> "ActiveVoiceState":
    """
    Connect the bot to this voice channel, or move the bot to this voice channel if it is already connected in another voice channel.

    Args:
        muted: Whether the bot should be muted when connected.
        deafened: Whether the bot should be deafened when connected.

    Returns:
        The new active voice state on successfully connection.

    """
    if not self.voice_state:
        return await self._client.connect_to_vc(self._guild_id, self.id, muted, deafened)
    await self.voice_state.move(self.id)
    return self.voice_state

disconnect() async

Disconnect from the currently connected voice state.

Raises:

Type Description
VoiceNotConnected

if the bot is not connected to a voice channel

Source code in interactions/models/discord/channel.py
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
async def disconnect(self) -> None:
    """
    Disconnect from the currently connected voice state.

    Raises:
        VoiceNotConnected: if the bot is not connected to a voice channel

    """
    if self.voice_state:
        return await self.voice_state.disconnect()
    raise VoiceNotConnected

edit(*, name=MISSING, position=MISSING, permission_overwrites=MISSING, parent_id=MISSING, bitrate=MISSING, user_limit=MISSING, rtc_region=MISSING, video_quality_mode=MISSING, reason=MISSING, **kwargs) async

Edit guild voice channel.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
position Absent[int]

the position of the channel in the left-hand listing

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

a list of PermissionOverwrite to apply to the channel

MISSING
parent_id Absent[Snowflake_Type]

the parent category Snowflake_Type for the channel

MISSING
bitrate Absent[int]

the bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)

MISSING
user_limit Absent[int]

the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit

MISSING
rtc_region Absent[str]

channel voice region id, automatic when not set

MISSING
video_quality_mode Absent[VideoQualityMode]

the camera video quality mode of the voice channel

MISSING
reason Absent[str]

optional reason for audit logs

MISSING

Returns:

Type Description
Union[GuildVoice, GuildStageVoice]

The edited voice channel object.

Source code in interactions/models/discord/channel.py
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
async def edit(
    self,
    *,
    name: Absent[str] = MISSING,
    position: Absent[int] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    parent_id: Absent[Snowflake_Type] = MISSING,
    bitrate: Absent[int] = MISSING,
    user_limit: Absent[int] = MISSING,
    rtc_region: Absent[str] = MISSING,
    video_quality_mode: Absent[VideoQualityMode] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> Union["GuildVoice", "GuildStageVoice"]:
    """
    Edit guild voice channel.

    Args:
        name: 1-100 character channel name
        position: the position of the channel in the left-hand listing
        permission_overwrites: a list of `PermissionOverwrite` to apply to the channel
        parent_id: the parent category `Snowflake_Type` for the channel
        bitrate: the bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)
        user_limit: the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit
        rtc_region: channel voice region id, automatic when not set
        video_quality_mode: the camera video quality mode of the voice channel
        reason: optional reason for audit logs

    Returns:
        The edited voice channel object.

    """
    return await super().edit(
        name=name,
        position=position,
        permission_overwrites=permission_overwrites,
        parent_id=parent_id,
        bitrate=bitrate,
        user_limit=user_limit,
        rtc_region=rtc_region,
        video_quality_mode=video_quality_mode,
        reason=reason,
        **kwargs,
    )

WebhookMixin

Source code in interactions/models/discord/channel.py
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
@attrs.define(eq=False, order=False, hash=False, slots=False, kw_only=True)
class WebhookMixin:
    async def create_webhook(self, name: str, avatar: Absent[UPLOADABLE_TYPE] = MISSING) -> "models.Webhook":
        """
        Create a webhook in this channel.

        Args:
            name: The name of the webhook
            avatar: An optional default avatar image to use

        Returns:
            The created webhook object

        Raises:
            ValueError: If you try to name the webhook "Clyde"

        """
        return await models.Webhook.create(self._client, self, name, avatar)  # type: ignore

    async def delete_webhook(self, webhook: "models.Webhook") -> None:
        """
        Delete a given webhook in this channel.

        Args:
            webhook: The webhook to delete

        """
        return await webhook.delete()

    async def fetch_webhooks(self) -> List["models.Webhook"]:
        """
        Fetches all the webhooks for this channel.

        Returns:
            List of webhook objects

        """
        resp = await self._client.http.get_channel_webhooks(self.id)
        return [models.Webhook.from_dict(d, self._client) for d in resp]

create_webhook(name, avatar=MISSING) async

Create a webhook in this channel.

Parameters:

Name Type Description Default
name str

The name of the webhook

required
avatar Absent[UPLOADABLE_TYPE]

An optional default avatar image to use

MISSING

Returns:

Type Description
Webhook

The created webhook object

Raises:

Type Description
ValueError

If you try to name the webhook "Clyde"

Source code in interactions/models/discord/channel.py
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
async def create_webhook(self, name: str, avatar: Absent[UPLOADABLE_TYPE] = MISSING) -> "models.Webhook":
    """
    Create a webhook in this channel.

    Args:
        name: The name of the webhook
        avatar: An optional default avatar image to use

    Returns:
        The created webhook object

    Raises:
        ValueError: If you try to name the webhook "Clyde"

    """
    return await models.Webhook.create(self._client, self, name, avatar)  # type: ignore

delete_webhook(webhook) async

Delete a given webhook in this channel.

Parameters:

Name Type Description Default
webhook Webhook

The webhook to delete

required
Source code in interactions/models/discord/channel.py
754
755
756
757
758
759
760
761
762
async def delete_webhook(self, webhook: "models.Webhook") -> None:
    """
    Delete a given webhook in this channel.

    Args:
        webhook: The webhook to delete

    """
    return await webhook.delete()

fetch_webhooks() async

Fetches all the webhooks for this channel.

Returns:

Type Description
List[Webhook]

List of webhook objects

Source code in interactions/models/discord/channel.py
764
765
766
767
768
769
770
771
772
773
async def fetch_webhooks(self) -> List["models.Webhook"]:
    """
    Fetches all the webhooks for this channel.

    Returns:
        List of webhook objects

    """
    resp = await self._client.http.get_channel_webhooks(self.id)
    return [models.Webhook.from_dict(d, self._client) for d in resp]

process_permission_overwrites(overwrites)

Processes a permission overwrite lists into format for sending to discord.

Parameters:

Name Type Description Default
overwrites Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]

The permission overwrites to process

required

Returns:

Type Description
List[dict]

The processed permission overwrites

Source code in interactions/models/discord/channel.py
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
def process_permission_overwrites(
    overwrites: Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
) -> List[dict]:
    """
    Processes a permission overwrite lists into format for sending to discord.

    Args:
        overwrites: The permission overwrites to process

    Returns:
        The processed permission overwrites

    """
    if not overwrites:
        return overwrites

    if isinstance(overwrites, dict):
        return [overwrites]

    if isinstance(overwrites, list):
        return list(map(to_dict, overwrites))

    if isinstance(overwrites, PermissionOverwrite):
        return [overwrites.to_dict()]

    raise ValueError(f"Invalid overwrites: {overwrites}")

Invite

Bases: ClientObject

Source code in interactions/models/discord/invite.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class Invite(ClientObject):
    code: str = attrs.field(repr=True)
    """The invite code (unique ID)"""

    # metadata
    uses: int = attrs.field(default=0, repr=True)
    """How many times this invite has been used"""
    max_uses: int = attrs.field(repr=False, default=0)
    """Max number of times this invite can be used"""
    max_age: int = attrs.field(repr=False, default=0)
    """Duration (in seconds) after which the invite expires"""
    created_at: Timestamp = attrs.field(default=MISSING, converter=optional_c(timestamp_converter), repr=True)
    """When this invite was created"""
    temporary: bool = attrs.field(default=False, repr=True)
    """Whether this invite only grants temporary membership"""

    # target data
    target_type: Optional[Union[InviteTargetType, int]] = attrs.field(
        default=None, converter=optional_c(InviteTargetType), repr=True
    )
    """The type of target for this voice channel invite"""
    approximate_presence_count: Optional[int] = attrs.field(repr=False, default=MISSING)
    """Approximate count of online members, returned when fetching invites with `with_counts` set as `True`"""
    approximate_member_count: Optional[int] = attrs.field(repr=False, default=MISSING)
    """Approximate count of total members, returned when fetching invites with `with_counts` set as `True`"""
    scheduled_event: Optional["Snowflake_Type"] = attrs.field(
        default=None, converter=optional_c(to_snowflake), repr=True
    )
    """Guild scheduled event data, only included if `guild_scheduled_event_id` contains a valid guild scheduled event id"""
    expires_at: Optional[Timestamp] = attrs.field(default=None, converter=optional_c(timestamp_converter), repr=True)
    """The expiration date of this invite, returned when fetching invites with `with_expiration` set as `True`"""
    stage_instance: Optional[StageInstance] = attrs.field(repr=False, default=None)
    """Stage instance data if there is a public Stage instance in the Stage channel this invite is for (deprecated)"""
    target_application: Optional[dict] = attrs.field(repr=False, default=None)
    """The embedded application to open for this voice channel embedded application invite"""
    guild_preview: Optional[GuildPreview] = attrs.field(repr=False, default=MISSING)
    """The guild this invite is for - not given in invite events"""

    # internal for props
    _channel_id: "Snowflake_Type" = attrs.field(converter=to_snowflake, repr=True)
    _guild_id: Optional["Snowflake_Type"] = attrs.field(default=None, converter=optional_c(to_snowflake), repr=True)
    _inviter_id: Optional["Snowflake_Type"] = attrs.field(default=None, converter=optional_c(to_snowflake), repr=True)
    _target_user_id: Optional["Snowflake_Type"] = attrs.field(
        repr=False, default=None, converter=optional_c(to_snowflake)
    )

    @property
    def channel(self) -> Optional["TYPE_GUILD_CHANNEL"]:
        """The cached channel the invite is for."""
        return self._client.cache.get_channel(self._channel_id)

    @property
    def guild(self) -> Optional["Guild"]:
        """The cached guild the invite is."""
        return self._client.cache.get_guild(self._guild_id) if self._guild_id else None

    @property
    def inviter(self) -> Optional["User"]:
        """The user that created the invite or None."""
        return self._client.cache.get_user(self._inviter_id) if self._inviter_id else None

    @property
    def target_user(self) -> Optional["User"]:
        """The user whose stream to display for this voice channel stream invite or None."""
        return self._client.cache.get_user(self._target_user_id) if self._target_user_id else None

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if "stage_instance" in data:
            data["stage_instance"] = StageInstance.from_dict(data, client)

        if "target_application" in data:
            data["target_application"] = Application.from_dict(data, client)

        if "target_event_id" in data:
            data["scheduled_event"] = data["target_event_id"]

        if channel := data.pop("channel", None):
            client.cache.place_channel_data(channel)
            data["channel_id"] = channel["id"]

        if guild := data.pop("guild", None):
            data["guild_preview"] = GuildPreview.from_dict(guild, client)
            data["guild_id"] = guild["id"]
        elif guild_id := data.pop("guild_id", None):
            data["guild_id"] = guild_id

        if inviter := data.pop("inviter", None):
            inviter = client.cache.place_user_data(inviter)
            data["inviter_id"] = inviter.id

        if target_user := data.pop("target_user", None):
            target_user = client.cache.place_user_data(target_user)
            data["target_user_id"] = target_user.id

        return data

    def __str__(self) -> str:
        return self.link

    @property
    def link(self) -> str:
        """The invite link."""
        if self.scheduled_event:
            return f"https://discord.gg/{self.code}?event={self.scheduled_event}"
        return f"https://discord.gg/{self.code}"

    async def delete(self, reason: Absent[str] = MISSING) -> None:
        """
        Delete this invite.

        !!! note
            You must have the `manage_channels` permission on the channel this invite belongs to.

        !!! note
            With `manage_guild` permission, you can delete any invite across the guild.

        Args:
            reason: The reason for the deletion of invite.

        """
        await self._client.http.delete_invite(self.code, reason=reason)

approximate_member_count: Optional[int] = attrs.field(repr=False, default=MISSING) class-attribute

Approximate count of total members, returned when fetching invites with with_counts set as True

approximate_presence_count: Optional[int] = attrs.field(repr=False, default=MISSING) class-attribute

Approximate count of online members, returned when fetching invites with with_counts set as True

channel: Optional[TYPE_GUILD_CHANNEL] property

The cached channel the invite is for.

code: str = attrs.field(repr=True) class-attribute

The invite code (unique ID)

created_at: Timestamp = attrs.field(default=MISSING, converter=optional_c(timestamp_converter), repr=True) class-attribute

When this invite was created

expires_at: Optional[Timestamp] = attrs.field(default=None, converter=optional_c(timestamp_converter), repr=True) class-attribute

The expiration date of this invite, returned when fetching invites with with_expiration set as True

guild: Optional[Guild] property

The cached guild the invite is.

guild_preview: Optional[GuildPreview] = attrs.field(repr=False, default=MISSING) class-attribute

The guild this invite is for - not given in invite events

inviter: Optional[User] property

The user that created the invite or None.

The invite link.

max_age: int = attrs.field(repr=False, default=0) class-attribute

Duration (in seconds) after which the invite expires

max_uses: int = attrs.field(repr=False, default=0) class-attribute

Max number of times this invite can be used

scheduled_event: Optional[Snowflake_Type] = attrs.field(default=None, converter=optional_c(to_snowflake), repr=True) class-attribute

Guild scheduled event data, only included if guild_scheduled_event_id contains a valid guild scheduled event id

stage_instance: Optional[StageInstance] = attrs.field(repr=False, default=None) class-attribute

Stage instance data if there is a public Stage instance in the Stage channel this invite is for (deprecated)

target_application: Optional[dict] = attrs.field(repr=False, default=None) class-attribute

The embedded application to open for this voice channel embedded application invite

target_type: Optional[Union[InviteTargetType, int]] = attrs.field(default=None, converter=optional_c(InviteTargetType), repr=True) class-attribute

The type of target for this voice channel invite

target_user: Optional[User] property

The user whose stream to display for this voice channel stream invite or None.

temporary: bool = attrs.field(default=False, repr=True) class-attribute

Whether this invite only grants temporary membership

uses: int = attrs.field(default=0, repr=True) class-attribute

How many times this invite has been used

delete(reason=MISSING) async

Delete this invite.

Note

You must have the manage_channels permission on the channel this invite belongs to.

Note

With manage_guild permission, you can delete any invite across the guild.

Parameters:

Name Type Description Default
reason Absent[str]

The reason for the deletion of invite.

MISSING
Source code in interactions/models/discord/invite.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
async def delete(self, reason: Absent[str] = MISSING) -> None:
    """
    Delete this invite.

    !!! note
        You must have the `manage_channels` permission on the channel this invite belongs to.

    !!! note
        With `manage_guild` permission, you can delete any invite across the guild.

    Args:
        reason: The reason for the deletion of invite.

    """
    await self._client.http.delete_invite(self.code, reason=reason)

Role

Bases: DiscordObject

Source code in interactions/models/discord/role.py
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
@total_ordering
class Role(DiscordObject):
    _sentinel = object()

    name: str = attrs.field(repr=True)
    color: "Color" = attrs.field(repr=False, converter=Color)
    hoist: bool = attrs.field(repr=False, default=False)
    position: int = attrs.field(repr=True)
    permissions: "Permissions" = attrs.field(repr=False, converter=Permissions)
    managed: bool = attrs.field(repr=False, default=False)
    mentionable: bool = attrs.field(repr=False, default=True)
    premium_subscriber: bool = attrs.field(
        repr=False, default=_sentinel, converter=partial(sentinel_converter, sentinel=_sentinel)
    )
    subscription_listing_id: "Snowflake_Type | None" = attrs.field(default=None, repr=False)
    purchasable_or_has_subscribers: bool = attrs.field(default=False)
    _icon: Asset | None = attrs.field(repr=False, default=None)
    _unicode_emoji: PartialEmoji | None = attrs.field(
        repr=False, default=None, converter=optional_c(PartialEmoji.from_str)
    )
    _guild_id: "Snowflake_Type" = attrs.field(
        repr=False,
    )
    _bot_id: "Snowflake_Type | None" = attrs.field(repr=False, default=None)
    _integration_id: "Snowflake_Type | None" = attrs.field(repr=False, default=None)  # todo integration object?
    _guild_connections: bool = attrs.field(repr=False, default=False)

    def __lt__(self: "Role", other: "Role") -> bool:
        if not isinstance(self, Role) or not isinstance(other, Role):
            return NotImplemented

        if self._guild_id != other._guild_id:
            raise RuntimeError("Unable to compare Roles from different guilds.")

        if self.id == self._guild_id:  # everyone role
            # everyone role is on the bottom, so check if the other role is, well, not it
            # because then it must be higher than it
            return other.id != self.id

        if self.position < other.position:
            return True

        return self.id < other.id if self.position == other.position else False

    @classmethod
    def _process_dict(cls, data: dict[str, Any], client: "Client") -> dict[str, Any]:
        data |= data.pop("tags", {})

        if icon_hash := data.get("icon"):
            data["icon"] = Asset.from_path_hash(client, f"role-icons/{data['id']}/{{}}", icon_hash)

        data["premium_subscriber"] = nulled_boolean_get(data, "premium_subscriber")
        data["guild_connections"] = nulled_boolean_get(data, "guild_connections")
        data["available_for_purchase"] = nulled_boolean_get(data, "available_for_purchase")

        return data

    async def fetch_bot(self, *, force: bool = False) -> "Member | None":
        """
        Fetch the bot associated with this role if any.

        Args:
            force: Whether to force fetch the bot from the API.

        Returns:
            Member object if any

        """
        if self._bot_id is None:
            return None
        return await self._client.cache.fetch_member(self._guild_id, self._bot_id, force=force)

    def get_bot(self) -> "Member | None":
        """
        Get the bot associated with this role if any.

        Returns:
            Member object if any

        """
        if self._bot_id is None:
            return None
        return self._client.cache.get_member(self._guild_id, self._bot_id)

    @property
    def guild(self) -> "Guild":
        """The guild object this role is from."""
        return self._client.cache.get_guild(self._guild_id)  # pyright: ignore [reportGeneralTypeIssues]

    @property
    def default(self) -> bool:
        """Is this the `@everyone` role."""
        return self.id == self._guild_id

    @property
    def bot_managed(self) -> bool:
        """Is this role owned/managed by a bot."""
        return self._bot_id is not None

    @property
    def is_linked_role(self) -> bool:
        """Is this role a linked role."""
        return self._guild_connections

    @property
    def mention(self) -> str:
        """Returns a string that would mention the role."""
        return f"<@&{self.id}>" if self.id != self._guild_id else "@everyone"

    @property
    def integration(self) -> bool:
        """Is this role owned/managed by an integration."""
        return self._integration_id is not None

    @property
    def members(self) -> list["Member"]:
        """List of members with this role"""
        return [member for member in self.guild.members if member.has_role(self)]

    @property
    def icon(self) -> Asset | PartialEmoji | None:
        """
        The icon of this role

        !!! note
            You have to use this method instead of the `_icon` attribute, because the first does account for unicode emojis
        """
        return self._icon or self._unicode_emoji

    @property
    def is_assignable(self) -> bool:
        """
        Can this role be assigned or removed by this bot?

        !!! note
            This does not account for permissions, only the role hierarchy

        """
        return (self.default or self.guild.me.top_role > self) and not self.managed

    async def delete(self, reason: str | Missing = MISSING) -> None:
        """
        Delete this role.

        Args:
            reason: An optional reason for this deletion

        """
        await self._client.http.delete_guild_role(self._guild_id, self.id, reason)

    async def edit(
        self,
        *,
        name: str | None = None,
        permissions: str | None = None,
        color: Color | COLOR_TYPES | None = None,
        hoist: bool | None = None,
        mentionable: bool | None = None,
        icon: bytes | UPLOADABLE_TYPE | None = None,
        unicode_emoji: str | None = None,
    ) -> "Role":
        """
        Edit this role, all arguments are optional.

        Args:
            name: name of the role
            permissions: New permissions to use
            color: The color of the role
            hoist: whether the role should be displayed separately in the sidebar
            mentionable: whether the role should be mentionable
            icon: (Guild Level 2+) Bytes-like object representing the icon; supports PNG, JPEG and WebP
            unicode_emoji: (Guild Level 2+) Unicode emoji for the role; can't be used with icon

        Returns:
            Role with updated information

        """
        color = process_color(color)

        if icon and unicode_emoji:
            raise ValueError("Cannot pass both icon and unicode_emoji")
        if icon:
            icon = to_image_data(icon)

        payload = dict_filter(
            {
                "name": name,
                "permissions": permissions,
                "color": color,
                "hoist": hoist,
                "mentionable": mentionable,
                "icon": icon,
                "unicode_emoji": unicode_emoji,
            }
        )

        r_data = await self._client.http.modify_guild_role(self._guild_id, self.id, payload)
        r_data = dict(r_data)  # to convert typed dict to regular dict
        r_data["guild_id"] = self._guild_id
        return self.from_dict(r_data, self._client)

    async def move(self, position: int, reason: str | Missing = MISSING) -> "Role":
        """
        Move this role to a new position.

        Args:
            position: The new position of the role
            reason: An optional reason for this move

        Returns:
            The role object

        """
        await self._client.http.modify_guild_role_positions(
            self._guild_id, [{"id": self.id, "position": position}], reason
        )
        return self

bot_managed: bool property

Is this role owned/managed by a bot.

default: bool property

Is this the @everyone role.

guild: Guild property

The guild object this role is from.

icon: Asset | PartialEmoji | None property

The icon of this role

Note

You have to use this method instead of the _icon attribute, because the first does account for unicode emojis

integration: bool property

Is this role owned/managed by an integration.

is_assignable: bool property

Can this role be assigned or removed by this bot?

Note

This does not account for permissions, only the role hierarchy

is_linked_role: bool property

Is this role a linked role.

members: list[Member] property

List of members with this role

mention: str property

Returns a string that would mention the role.

delete(reason=MISSING) async

Delete this role.

Parameters:

Name Type Description Default
reason str | Missing

An optional reason for this deletion

MISSING
Source code in interactions/models/discord/role.py
172
173
174
175
176
177
178
179
180
async def delete(self, reason: str | Missing = MISSING) -> None:
    """
    Delete this role.

    Args:
        reason: An optional reason for this deletion

    """
    await self._client.http.delete_guild_role(self._guild_id, self.id, reason)

edit(*, name=None, permissions=None, color=None, hoist=None, mentionable=None, icon=None, unicode_emoji=None) async

Edit this role, all arguments are optional.

Parameters:

Name Type Description Default
name str | None

name of the role

None
permissions str | None

New permissions to use

None
color Color | COLOR_TYPES | None

The color of the role

None
hoist bool | None

whether the role should be displayed separately in the sidebar

None
mentionable bool | None

whether the role should be mentionable

None
icon bytes | UPLOADABLE_TYPE | None

(Guild Level 2+) Bytes-like object representing the icon; supports PNG, JPEG and WebP

None
unicode_emoji str | None

(Guild Level 2+) Unicode emoji for the role; can't be used with icon

None

Returns:

Type Description
Role

Role with updated information

Source code in interactions/models/discord/role.py
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
async def edit(
    self,
    *,
    name: str | None = None,
    permissions: str | None = None,
    color: Color | COLOR_TYPES | None = None,
    hoist: bool | None = None,
    mentionable: bool | None = None,
    icon: bytes | UPLOADABLE_TYPE | None = None,
    unicode_emoji: str | None = None,
) -> "Role":
    """
    Edit this role, all arguments are optional.

    Args:
        name: name of the role
        permissions: New permissions to use
        color: The color of the role
        hoist: whether the role should be displayed separately in the sidebar
        mentionable: whether the role should be mentionable
        icon: (Guild Level 2+) Bytes-like object representing the icon; supports PNG, JPEG and WebP
        unicode_emoji: (Guild Level 2+) Unicode emoji for the role; can't be used with icon

    Returns:
        Role with updated information

    """
    color = process_color(color)

    if icon and unicode_emoji:
        raise ValueError("Cannot pass both icon and unicode_emoji")
    if icon:
        icon = to_image_data(icon)

    payload = dict_filter(
        {
            "name": name,
            "permissions": permissions,
            "color": color,
            "hoist": hoist,
            "mentionable": mentionable,
            "icon": icon,
            "unicode_emoji": unicode_emoji,
        }
    )

    r_data = await self._client.http.modify_guild_role(self._guild_id, self.id, payload)
    r_data = dict(r_data)  # to convert typed dict to regular dict
    r_data["guild_id"] = self._guild_id
    return self.from_dict(r_data, self._client)

fetch_bot(*, force=False) async

Fetch the bot associated with this role if any.

Parameters:

Name Type Description Default
force bool

Whether to force fetch the bot from the API.

False

Returns:

Type Description
Member | None

Member object if any

Source code in interactions/models/discord/role.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
async def fetch_bot(self, *, force: bool = False) -> "Member | None":
    """
    Fetch the bot associated with this role if any.

    Args:
        force: Whether to force fetch the bot from the API.

    Returns:
        Member object if any

    """
    if self._bot_id is None:
        return None
    return await self._client.cache.fetch_member(self._guild_id, self._bot_id, force=force)

get_bot()

Get the bot associated with this role if any.

Returns:

Type Description
Member | None

Member object if any

Source code in interactions/models/discord/role.py
104
105
106
107
108
109
110
111
112
113
114
def get_bot(self) -> "Member | None":
    """
    Get the bot associated with this role if any.

    Returns:
        Member object if any

    """
    if self._bot_id is None:
        return None
    return self._client.cache.get_member(self._guild_id, self._bot_id)

move(position, reason=MISSING) async

Move this role to a new position.

Parameters:

Name Type Description Default
position int

The new position of the role

required
reason str | Missing

An optional reason for this move

MISSING

Returns:

Type Description
Role

The role object

Source code in interactions/models/discord/role.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
async def move(self, position: int, reason: str | Missing = MISSING) -> "Role":
    """
    Move this role to a new position.

    Args:
        position: The new position of the role
        reason: An optional reason for this move

    Returns:
        The role object

    """
    await self._client.http.modify_guild_role_positions(
        self._guild_id, [{"id": self.id, "position": position}], reason
    )
    return self

CustomEmoji

Bases: PartialEmoji, ClientObject

Represent a custom emoji in a guild with all its properties.

Source code in interactions/models/discord/emoji.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class CustomEmoji(PartialEmoji, ClientObject):
    """Represent a custom emoji in a guild with all its properties."""

    _client: "Client" = attrs.field(repr=False, metadata=no_export_meta)

    require_colons: bool = attrs.field(repr=False, default=False)
    """Whether this emoji must be wrapped in colons"""
    managed: bool = attrs.field(repr=False, default=False)
    """Whether this emoji is managed"""
    available: bool = attrs.field(repr=False, default=False)
    """Whether this emoji can be used, may be false due to loss of Server Boosts."""

    _creator_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None, converter=optional(to_snowflake))
    _role_ids: List["Snowflake_Type"] = attrs.field(
        repr=False, factory=list, converter=optional(list_converter(to_snowflake))
    )
    _guild_id: "Snowflake_Type" = attrs.field(repr=False, default=None, converter=to_snowflake)

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        creator_dict = data.pop("user", None)
        data["creator_id"] = client.cache.place_user_data(creator_dict).id if creator_dict else None

        if "roles" in data:
            data["role_ids"] = data.pop("roles")

        return data

    @classmethod
    def from_dict(cls, data: Dict[str, Any], client: "Client", guild_id: int) -> "CustomEmoji":
        data = cls._process_dict(data, client)
        return cls(client=client, guild_id=guild_id, **cls._filter_kwargs(data, cls._get_init_keys()))

    @property
    def guild(self) -> "Guild":
        """The guild this emoji belongs to."""
        return self._client.cache.get_guild(self._guild_id)

    @property
    def creator(self) -> Optional[Union["Member", "User"]]:
        """The member that created this emoji."""
        return self._client.cache.get_member(self._creator_id, self._guild_id) or self._client.cache.get_user(
            self._creator_id
        )

    @property
    def roles(self) -> List["Role"]:
        """The roles allowed to use this emoji."""
        return [self._client.cache.get_role(role_id) for role_id in self._role_ids]

    @property
    def is_usable(self) -> bool:
        """Determines if this emoji is usable by the current user."""
        if not self.available:
            return False

        guild = self.guild
        return any(e_role_id in guild.me._role_ids for e_role_id in self._role_ids)

    async def edit(
        self,
        *,
        name: Optional[str] = None,
        roles: Optional[List[Union["Snowflake_Type", "Role"]]] = None,
        reason: Optional[str] = None,
    ) -> "CustomEmoji":
        """
        Modify the custom emoji information.

        Args:
            name: The name of the emoji.
            roles: The roles allowed to use this emoji.
            reason: Attach a reason to this action, used for audit logs.

        Returns:
            The newly modified custom emoji.

        """
        data_payload = dict_filter_none(
            {
                "name": name,
                "roles": to_snowflake_list(roles) if roles else None,
            }
        )

        updated_data = await self._client.http.modify_guild_emoji(data_payload, self._guild_id, self.id, reason=reason)
        self.update_from_dict(updated_data)
        return self

    async def delete(self, reason: Optional[str] = None) -> None:
        """
        Deletes the custom emoji from the guild.

        Args:
            reason: Attach a reason to this action, used for audit logs.

        """
        if not self._guild_id:
            raise ValueError("Cannot delete emoji, no guild id set.")

        await self._client.http.delete_guild_emoji(self._guild_id, self.id, reason=reason)

    @property
    def url(self) -> str:
        """CDN url for the emoji."""
        return f"https://cdn.discordapp.com/emojis/{self.id}.{'gif' if self.animated else 'png'}"

available: bool = attrs.field(repr=False, default=False) class-attribute

Whether this emoji can be used, may be false due to loss of Server Boosts.

creator: Optional[Union[Member, User]] property

The member that created this emoji.

guild: Guild property

The guild this emoji belongs to.

is_usable: bool property

Determines if this emoji is usable by the current user.

managed: bool = attrs.field(repr=False, default=False) class-attribute

Whether this emoji is managed

require_colons: bool = attrs.field(repr=False, default=False) class-attribute

Whether this emoji must be wrapped in colons

roles: List[Role] property

The roles allowed to use this emoji.

url: str property

CDN url for the emoji.

delete(reason=None) async

Deletes the custom emoji from the guild.

Parameters:

Name Type Description Default
reason Optional[str]

Attach a reason to this action, used for audit logs.

None
Source code in interactions/models/discord/emoji.py
196
197
198
199
200
201
202
203
204
205
206
207
async def delete(self, reason: Optional[str] = None) -> None:
    """
    Deletes the custom emoji from the guild.

    Args:
        reason: Attach a reason to this action, used for audit logs.

    """
    if not self._guild_id:
        raise ValueError("Cannot delete emoji, no guild id set.")

    await self._client.http.delete_guild_emoji(self._guild_id, self.id, reason=reason)

edit(*, name=None, roles=None, reason=None) async

Modify the custom emoji information.

Parameters:

Name Type Description Default
name Optional[str]

The name of the emoji.

None
roles Optional[List[Union[Snowflake_Type, Role]]]

The roles allowed to use this emoji.

None
reason Optional[str]

Attach a reason to this action, used for audit logs.

None

Returns:

Type Description
CustomEmoji

The newly modified custom emoji.

Source code in interactions/models/discord/emoji.py
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
async def edit(
    self,
    *,
    name: Optional[str] = None,
    roles: Optional[List[Union["Snowflake_Type", "Role"]]] = None,
    reason: Optional[str] = None,
) -> "CustomEmoji":
    """
    Modify the custom emoji information.

    Args:
        name: The name of the emoji.
        roles: The roles allowed to use this emoji.
        reason: Attach a reason to this action, used for audit logs.

    Returns:
        The newly modified custom emoji.

    """
    data_payload = dict_filter_none(
        {
            "name": name,
            "roles": to_snowflake_list(roles) if roles else None,
        }
    )

    updated_data = await self._client.http.modify_guild_emoji(data_payload, self._guild_id, self.id, reason=reason)
    self.update_from_dict(updated_data)
    return self

PartialEmoji

Bases: SnowflakeObject, DictSerializationMixin

Represent a basic ("partial") emoji used in discord.

Source code in interactions/models/discord/emoji.py
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class PartialEmoji(SnowflakeObject, DictSerializationMixin):
    """Represent a basic ("partial") emoji used in discord."""

    id: Optional["Snowflake_Type"] = attrs.field(
        repr=True, default=None, converter=optional(to_snowflake)
    )  # can be None for Standard Emoji
    """The custom emoji id. Leave empty if you are using standard unicode emoji."""
    name: Optional[str] = attrs.field(repr=True, default=None)
    """The custom emoji name, or standard unicode emoji in string"""
    animated: bool = attrs.field(repr=True, default=False)
    """Whether this emoji is animated"""

    @classmethod
    def from_str(cls, emoji_str: str, *, language: str = "alias") -> Optional["PartialEmoji"]:
        """
        Generate a PartialEmoji from a discord Emoji string representation, or unicode emoji.

        Handles:
            <:emoji_name:emoji_id>
            :emoji_name:emoji_id
            <a:emoji_name:emoji_id>
            a:emoji_name:emoji_id
            👋
            :wave:

        Args:
            emoji_str: The string representation an emoji
            language: The language to use for the unicode emoji parsing

        Returns:
            A PartialEmoji object

        Raises:
            ValueError: if the string cannot be parsed

        """
        if parsed := emoji_regex.findall(emoji_str):
            parsed = tuple(filter(None, parsed[0]))
            if len(parsed) == 3:
                return cls(name=parsed[1], id=parsed[2], animated=True)
            if len(parsed) == 2:
                return cls(name=parsed[0], id=parsed[1])
            _name = emoji.emojize(emoji_str, language=language)
            if _emoji_list := emoji.distinct_emoji_list(_name):
                return cls(name=_emoji_list[0])
        else:
            if _emoji_list := emoji.distinct_emoji_list(emoji_str):
                return cls(name=_emoji_list[0])

            # the emoji lib handles *most* emoji, however there are certain ones that it misses
            # this acts as a fallback check
            if matches := unicode_emoji_reg.search(emoji_str):
                match = matches.group()

                # the regex will match certain special characters, so this acts as a final failsafe
                if match not in string.printable and unicodedata.category(match) == "So":
                    return cls(name=match)
        return None

    def __str__(self) -> str:
        s = self.req_format
        if self.id:
            s = f"<{'a:' if self.animated else ':'}{s}>"
        return s

    def __eq__(self, other) -> bool:
        if not isinstance(other, PartialEmoji):
            return False
        return self.id == other.id if self.id else self.name == other.name

    @property
    def req_format(self) -> str:
        """Format used for web request."""
        return f"{self.name}:{self.id}" if self.id else self.name

animated: bool = attrs.field(repr=True, default=False) class-attribute

Whether this emoji is animated

id: Optional[Snowflake_Type] = attrs.field(repr=True, default=None, converter=optional(to_snowflake)) class-attribute

The custom emoji id. Leave empty if you are using standard unicode emoji.

name: Optional[str] = attrs.field(repr=True, default=None) class-attribute

The custom emoji name, or standard unicode emoji in string

req_format: str property

Format used for web request.

from_str(emoji_str, *, language='alias') classmethod

Generate a PartialEmoji from a discord Emoji string representation, or unicode emoji.

Handles

<:emoji_name:emoji_id> :emoji_name:emoji_id a:emoji_name:emoji_id 👋 👋

Parameters:

Name Type Description Default
emoji_str str

The string representation an emoji

required
language str

The language to use for the unicode emoji parsing

'alias'

Returns:

Type Description
Optional[PartialEmoji]

A PartialEmoji object

Raises:

Type Description
ValueError

if the string cannot be parsed

Source code in interactions/models/discord/emoji.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
@classmethod
def from_str(cls, emoji_str: str, *, language: str = "alias") -> Optional["PartialEmoji"]:
    """
    Generate a PartialEmoji from a discord Emoji string representation, or unicode emoji.

    Handles:
        <:emoji_name:emoji_id>
        :emoji_name:emoji_id
        <a:emoji_name:emoji_id>
        a:emoji_name:emoji_id
        👋
        :wave:

    Args:
        emoji_str: The string representation an emoji
        language: The language to use for the unicode emoji parsing

    Returns:
        A PartialEmoji object

    Raises:
        ValueError: if the string cannot be parsed

    """
    if parsed := emoji_regex.findall(emoji_str):
        parsed = tuple(filter(None, parsed[0]))
        if len(parsed) == 3:
            return cls(name=parsed[1], id=parsed[2], animated=True)
        if len(parsed) == 2:
            return cls(name=parsed[0], id=parsed[1])
        _name = emoji.emojize(emoji_str, language=language)
        if _emoji_list := emoji.distinct_emoji_list(_name):
            return cls(name=_emoji_list[0])
    else:
        if _emoji_list := emoji.distinct_emoji_list(emoji_str):
            return cls(name=_emoji_list[0])

        # the emoji lib handles *most* emoji, however there are certain ones that it misses
        # this acts as a fallback check
        if matches := unicode_emoji_reg.search(emoji_str):
            match = matches.group()

            # the regex will match certain special characters, so this acts as a final failsafe
            if match not in string.printable and unicodedata.category(match) == "So":
                return cls(name=match)
    return None

process_emoji(emoji)

Processes the emoji parameter into the dictionary format required by the API.

Parameters:

Name Type Description Default
emoji Optional[Union[PartialEmoji, dict, str]]

The emoji to process.

required

Returns:

Type Description
Optional[dict]

formatted dictionary for discord

Source code in interactions/models/discord/emoji.py
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
def process_emoji(emoji: Optional[Union[PartialEmoji, dict, str]]) -> Optional[dict]:
    """
    Processes the emoji parameter into the dictionary format required by the API.

    Args:
        emoji: The emoji to process.

    Returns:
        formatted dictionary for discord

    """
    if not emoji:
        return emoji

    if isinstance(emoji, dict):
        return emoji

    if isinstance(emoji, str):
        emoji = PartialEmoji.from_str(emoji)

    if isinstance(emoji, PartialEmoji):
        return emoji.to_dict()

    raise ValueError(f"Invalid emoji: {emoji}")

process_emoji_req_format(emoji)

Processes the emoji parameter into the str format required by the API.

Parameters:

Name Type Description Default
emoji Optional[Union[PartialEmoji, dict, str]]

The emoji to process.

required

Returns:

Type Description
Optional[str]

formatted string for discord

Source code in interactions/models/discord/emoji.py
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
def process_emoji_req_format(emoji: Optional[Union[PartialEmoji, dict, str]]) -> Optional[str]:
    """
    Processes the emoji parameter into the str format required by the API.

    Args:
        emoji: The emoji to process.

    Returns:
        formatted string for discord

    """
    if not emoji:
        return emoji

    if isinstance(emoji, str):
        emoji = PartialEmoji.from_str(emoji)

    if isinstance(emoji, dict):
        emoji = PartialEmoji.from_dict(emoji)

    if isinstance(emoji, PartialEmoji):
        return emoji.req_format

    raise ValueError(f"Invalid emoji: {emoji}")

Message

AllowedMentions

Bases: DictSerializationMixin

The allowed mention field allows for more granular control over mentions without various hacks to the message content.

This will always validate against message content to avoid phantom pings, and check against user/bot permissions.

Source code in interactions/models/discord/message.py
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class AllowedMentions(DictSerializationMixin):
    """
    The allowed mention field allows for more granular control over mentions without various hacks to the message content.

    This will always validate against message content to avoid phantom
    pings, and check against user/bot permissions.

    """

    parse: Optional[List[str]] = attrs.field(repr=False, factory=list)
    """An array of allowed mention types to parse from the content."""
    roles: Optional[List["Snowflake_Type"]] = attrs.field(repr=False, factory=list, converter=to_snowflake_list)
    """Array of role_ids to mention. (Max size of 100)"""
    users: Optional[List["Snowflake_Type"]] = attrs.field(repr=False, factory=list, converter=to_snowflake_list)
    """Array of user_ids to mention. (Max size of 100)"""
    replied_user = attrs.field(repr=False, default=False)
    """For replies, whether to mention the author of the message being replied to. (default false)"""

    def add_parse(self, *mention_types: Union["MentionType", str]) -> None:
        """
        Add a mention type to the list of allowed mentions to parse.

        Args:
            *mention_types: The types of mentions to add

        """
        for mention_type in mention_types:
            if not isinstance(mention_type, MentionType) and mention_type not in MentionType.__members__.values():
                raise ValueError(f"Invalid mention type: {mention_type}")
            self.parse.append(mention_type)

    def add_roles(self, *roles: Union["models.Role", "Snowflake_Type"]) -> None:
        """
        Add roles that are allowed to be mentioned.

        Args:
            *roles: The roles to add

        """
        for role in roles:
            self.roles.append(to_snowflake(role))

    def add_users(self, *users: Union["models.Member", "models.BaseUser", "Snowflake_Type"]) -> None:
        """
        Add users that are allowed to be mentioned.

        Args:
            *users: The users to add

        """
        for user in users:
            self.users.append(to_snowflake(user))

    @classmethod
    def all(cls) -> "AllowedMentions":
        """
        Allows every user and role to be mentioned.

        Returns:
            An AllowedMentions object

        """
        return cls(parse=list(MentionType.__members__.values()), replied_user=True)

    @classmethod
    def none(cls) -> "AllowedMentions":
        """
        Disallows any user or role to be mentioned.

        Returns:
            An AllowedMentions object

        """
        return cls()

parse: Optional[List[str]] = attrs.field(repr=False, factory=list) class-attribute

An array of allowed mention types to parse from the content.

replied_user = attrs.field(repr=False, default=False) class-attribute

For replies, whether to mention the author of the message being replied to. (default false)

roles: Optional[List[Snowflake_Type]] = attrs.field(repr=False, factory=list, converter=to_snowflake_list) class-attribute

Array of role_ids to mention. (Max size of 100)

users: Optional[List[Snowflake_Type]] = attrs.field(repr=False, factory=list, converter=to_snowflake_list) class-attribute

Array of user_ids to mention. (Max size of 100)

add_parse(*mention_types)

Add a mention type to the list of allowed mentions to parse.

Parameters:

Name Type Description Default
*mention_types Union[MentionType, str]

The types of mentions to add

()
Source code in interactions/models/discord/message.py
300
301
302
303
304
305
306
307
308
309
310
311
def add_parse(self, *mention_types: Union["MentionType", str]) -> None:
    """
    Add a mention type to the list of allowed mentions to parse.

    Args:
        *mention_types: The types of mentions to add

    """
    for mention_type in mention_types:
        if not isinstance(mention_type, MentionType) and mention_type not in MentionType.__members__.values():
            raise ValueError(f"Invalid mention type: {mention_type}")
        self.parse.append(mention_type)

add_roles(*roles)

Add roles that are allowed to be mentioned.

Parameters:

Name Type Description Default
*roles Union[Role, Snowflake_Type]

The roles to add

()
Source code in interactions/models/discord/message.py
313
314
315
316
317
318
319
320
321
322
def add_roles(self, *roles: Union["models.Role", "Snowflake_Type"]) -> None:
    """
    Add roles that are allowed to be mentioned.

    Args:
        *roles: The roles to add

    """
    for role in roles:
        self.roles.append(to_snowflake(role))

add_users(*users)

Add users that are allowed to be mentioned.

Parameters:

Name Type Description Default
*users Union[Member, BaseUser, Snowflake_Type]

The users to add

()
Source code in interactions/models/discord/message.py
324
325
326
327
328
329
330
331
332
333
def add_users(self, *users: Union["models.Member", "models.BaseUser", "Snowflake_Type"]) -> None:
    """
    Add users that are allowed to be mentioned.

    Args:
        *users: The users to add

    """
    for user in users:
        self.users.append(to_snowflake(user))

all() classmethod

Allows every user and role to be mentioned.

Returns:

Type Description
AllowedMentions

An AllowedMentions object

Source code in interactions/models/discord/message.py
335
336
337
338
339
340
341
342
343
344
@classmethod
def all(cls) -> "AllowedMentions":
    """
    Allows every user and role to be mentioned.

    Returns:
        An AllowedMentions object

    """
    return cls(parse=list(MentionType.__members__.values()), replied_user=True)

none() classmethod

Disallows any user or role to be mentioned.

Returns:

Type Description
AllowedMentions

An AllowedMentions object

Source code in interactions/models/discord/message.py
346
347
348
349
350
351
352
353
354
355
@classmethod
def none(cls) -> "AllowedMentions":
    """
    Disallows any user or role to be mentioned.

    Returns:
        An AllowedMentions object

    """
    return cls()

Attachment

Bases: DiscordObject

Source code in interactions/models/discord/message.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class Attachment(DiscordObject):
    filename: str = attrs.field(
        repr=False,
    )
    """name of file attached"""
    description: Optional[str] = attrs.field(repr=False, default=None)
    """description for the file"""
    content_type: Optional[str] = attrs.field(repr=False, default=None)
    """the attachment's media type"""
    size: int = attrs.field(
        repr=False,
    )
    """size of file in bytes"""
    url: str = attrs.field(
        repr=False,
    )
    """source url of file"""
    proxy_url: str = attrs.field(
        repr=False,
    )
    """a proxied url of file"""
    height: Optional[int] = attrs.field(repr=False, default=None)
    """height of file (if image)"""
    width: Optional[int] = attrs.field(repr=False, default=None)
    """width of file (if image)"""
    ephemeral: bool = attrs.field(repr=False, default=False)
    """whether this attachment is ephemeral"""
    duration_secs: Optional[int] = attrs.field(repr=False, default=None)
    """the duration of the audio file (currently for voice messages)"""
    waveform: bytearray = attrs.field(repr=False, default=None)
    """base64 encoded bytearray representing a sampled waveform (currently for voice messages)"""

    @property
    def resolution(self) -> tuple[Optional[int], Optional[int]]:
        """Returns the image resolution of the attachment file"""
        return self.height, self.width

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], _) -> Dict[str, Any]:
        if waveform := data.pop("waveform", None):
            data["waveform"] = bytearray(base64.b64decode(waveform))
        return data

content_type: Optional[str] = attrs.field(repr=False, default=None) class-attribute

the attachment's media type

description: Optional[str] = attrs.field(repr=False, default=None) class-attribute

description for the file

duration_secs: Optional[int] = attrs.field(repr=False, default=None) class-attribute

the duration of the audio file (currently for voice messages)

ephemeral: bool = attrs.field(repr=False, default=False) class-attribute

whether this attachment is ephemeral

filename: str = attrs.field(repr=False) class-attribute

name of file attached

height: Optional[int] = attrs.field(repr=False, default=None) class-attribute

height of file (if image)

proxy_url: str = attrs.field(repr=False) class-attribute

a proxied url of file

resolution: tuple[Optional[int], Optional[int]] property

Returns the image resolution of the attachment file

size: int = attrs.field(repr=False) class-attribute

size of file in bytes

url: str = attrs.field(repr=False) class-attribute

source url of file

waveform: bytearray = attrs.field(repr=False, default=None) class-attribute

base64 encoded bytearray representing a sampled waveform (currently for voice messages)

width: Optional[int] = attrs.field(repr=False, default=None) class-attribute

width of file (if image)

BaseMessage

Bases: DiscordObject

Source code in interactions/models/discord/message.py
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class BaseMessage(DiscordObject):
    _channel_id: "Snowflake_Type" = attrs.field(repr=False, default=MISSING, converter=to_optional_snowflake)
    _thread_channel_id: Optional["Snowflake_Type"] = attrs.field(
        repr=False, default=None, converter=to_optional_snowflake
    )
    _guild_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None, converter=to_optional_snowflake)
    _author_id: "Snowflake_Type" = attrs.field(repr=False, default=MISSING, converter=to_optional_snowflake)

    @property
    def guild(self) -> "models.Guild":
        """The guild the message was sent in"""
        return self._client.cache.get_guild(self._guild_id)

    @property
    def channel(self) -> "models.TYPE_MESSAGEABLE_CHANNEL":
        """The channel the message was sent in"""
        channel = self._client.cache.get_channel(self._channel_id)

        if not self._guild_id and not channel:
            # allow dm operations without fetching a dm channel from API
            channel = BaseChannel.from_dict_factory({"id": self._channel_id, "type": ChannelType.DM}, self._client)
            if self.author:
                channel.recipients = [self.author]
        return channel

    @property
    def thread(self) -> "models.TYPE_THREAD_CHANNEL":
        """The thread that was started from this message, includes thread member object"""
        return self._client.cache.get_channel(self._thread_channel_id)

    @property
    def author(self) -> Union["models.Member", "models.User"]:
        """The author of this message. Only a valid user in the case where the message is generated by a user or bot user."""
        if self._author_id:
            member = None
            if self._guild_id:
                member = self._client.cache.get_member(self._guild_id, self._author_id)
            return member or self._client.cache.get_user(self._author_id)
        return MISSING

author: Union[models.Member, models.User] property

The author of this message. Only a valid user in the case where the message is generated by a user or bot user.

channel: models.TYPE_MESSAGEABLE_CHANNEL property

The channel the message was sent in

guild: models.Guild property

The guild the message was sent in

thread: models.TYPE_THREAD_CHANNEL property

The thread that was started from this message, includes thread member object

ChannelMention

Bases: DiscordObject

Source code in interactions/models/discord/message.py
151
152
153
154
155
156
157
158
159
160
161
162
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ChannelMention(DiscordObject):
    guild_id: "Snowflake_Type | None" = attrs.field(
        repr=False,
    )
    """id of the guild containing the channel"""
    type: ChannelType = attrs.field(repr=False, converter=ChannelType)
    """the type of channel"""
    name: str = attrs.field(
        repr=False,
    )
    """the name of the channel"""

guild_id: Snowflake_Type | None = attrs.field(repr=False) class-attribute

id of the guild containing the channel

name: str = attrs.field(repr=False) class-attribute

the name of the channel

type: ChannelType = attrs.field(repr=False, converter=ChannelType) class-attribute

the type of channel

Message

Bases: BaseMessage

Source code in interactions/models/discord/message.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class Message(BaseMessage):
    content: str = attrs.field(repr=False, default=MISSING)
    """Contents of the message"""
    timestamp: "models.Timestamp" = attrs.field(repr=False, default=MISSING, converter=optional_c(timestamp_converter))
    """When this message was sent"""
    edited_timestamp: Optional["models.Timestamp"] = attrs.field(
        repr=False, default=None, converter=optional_c(timestamp_converter)
    )
    """When this message was edited (or `None` if never)"""
    tts: bool = attrs.field(repr=False, default=False)
    """Whether this was a TTS message"""
    mention_everyone: bool = attrs.field(repr=False, default=False)
    """Whether this message mentions everyone"""
    mention_channels: List[ChannelMention] = attrs.field(repr=False, factory=list)
    """Channels specifically mentioned in this message"""
    attachments: List[Attachment] = attrs.field(repr=False, factory=list)
    """Any attached files"""
    embeds: List["models.Embed"] = attrs.field(repr=False, factory=list)
    """Any embedded content"""
    reactions: List["models.Reaction"] = attrs.field(repr=False, factory=list)
    """Reactions to the message"""
    nonce: Optional[Union[int, str]] = attrs.field(repr=False, default=None)
    """Used for validating a message was sent"""
    pinned: bool = attrs.field(repr=False, default=False)
    """Whether this message is pinned"""
    webhook_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None, converter=to_optional_snowflake)
    """If the message is generated by a webhook, this is the webhook's id"""
    type: MessageType = attrs.field(repr=False, default=MISSING, converter=optional_c(MessageType))
    """Type of message"""
    activity: Optional[MessageActivity] = attrs.field(repr=False, default=None, converter=optional_c(MessageActivity))
    """Activity sent with Rich Presence-related chat embeds"""
    application: Optional["models.Application"] = attrs.field(repr=False, default=None)  # TODO: partial application
    """Application sent with Rich Presence-related chat embeds"""
    application_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None, converter=to_optional_snowflake)
    """If the message is an Interaction or application-owned webhook, this is the id of the application"""
    message_reference: Optional[MessageReference] = attrs.field(
        repr=False, default=None, converter=optional_c(MessageReference.from_dict)
    )
    """Data showing the source of a crosspost, channel follow add, pin, or reply message"""
    flags: MessageFlags = attrs.field(repr=False, default=MessageFlags.NONE, converter=MessageFlags)
    """Message flags combined as a bitfield"""
    poll: Optional[Poll] = attrs.field(repr=False, default=None, converter=optional_c(Poll.from_dict))
    """A poll."""
    interaction_metadata: Optional[MessageInteractionMetadata] = attrs.field(repr=False, default=None)
    """Sent if the message is a response to an Interaction"""
    interaction: Optional["MessageInteraction"] = attrs.field(repr=False, default=None)
    """(Deprecated in favor of interaction_metadata) Sent if the message is a response to an Interaction"""
    components: Optional[List["models.ActionRow"]] = attrs.field(repr=False, default=None)
    """Sent if the message contains components like buttons, action rows, or other interactive components"""
    sticker_items: Optional[List["models.StickerItem"]] = attrs.field(repr=False, default=None)
    """Sent if the message contains stickers"""
    _mention_ids: List["Snowflake_Type"] = attrs.field(repr=False, factory=list)
    _mention_roles: List["Snowflake_Type"] = attrs.field(repr=False, factory=list)
    _referenced_message_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None)

    @property
    async def mention_users(self) -> AsyncGenerator[Union["models.Member", "models.User"], None]:
        """A generator of users mentioned in this message"""
        for u_id in self._mention_ids:
            if self._guild_id:
                yield await self._client.cache.fetch_member(self._guild_id, u_id)
            else:
                yield await self._client.cache.fetch_user(u_id)

    @property
    async def mention_roles(self) -> AsyncGenerator["models.Role", None]:
        """A generator of roles mentioned in this message"""
        for r_id in self._mention_roles:
            yield await self._client.cache.fetch_role(self._guild_id, r_id)

    @property
    def thread(self) -> "models.TYPE_THREAD_CHANNEL":
        """The thread that was started from this message, if any"""
        return self._client.cache.get_channel(self.id)

    @property
    def editable(self) -> bool:
        """Whether this message can be edited by the current user"""
        if self.author.id == self._client.user.id:
            return MessageFlags.VOICE_MESSAGE not in self.flags
        return False

    async def fetch_referenced_message(self, *, force: bool = False) -> Optional["Message"]:
        """
        Fetch the message this message is referencing, if any.

        Args:
            force: Whether to force a fetch from the API

        Returns:
            The referenced message, if found

        """
        if self._referenced_message_id is None:
            return None

        try:
            return await self._client.cache.fetch_message(self._channel_id, self._referenced_message_id, force=force)
        except NotFound:
            return None

    def get_referenced_message(self) -> Optional["Message"]:
        """
        Get the message this message is referencing, if any.

        Returns:
            The referenced message, if found

        """
        if self._referenced_message_id is None:
            return None
        return self._client.cache.get_message(self._channel_id, self._referenced_message_id)

    def contains_mention(
        self,
        query: "str | re.Pattern[str] | models.BaseUser | models.BaseChannel | models.Role",
        *,
        tag_as_mention: bool = False,
    ) -> bool:
        """
        Check whether the message contains the query or not.

        Args:
            query: The query to search for
            tag_as_mention: Should `BaseUser.tag` be checked *(only if query is an instance of BaseUser)*

        Returns:
            A boolean indicating whether the query could be found or not

        """
        return mentions(text=self.content or self.system_content, query=query, tag_as_mention=tag_as_mention)

    @classmethod
    def _process_dict(cls, data: dict, client: "Client") -> dict:  # noqa: C901
        if author_data := data.pop("author", None):
            if "guild_id" in data and "member" in data:
                author_data["member"] = data.pop("member")
                data["author_id"] = client.cache.place_member_data(data["guild_id"], author_data).id
            else:
                data["author_id"] = client.cache.place_user_data(author_data).id

        if mentions_data := data.pop("mentions", None):
            mention_ids = []
            for user_data in mentions_data:
                if "guild_id" in data and "member" in user_data:
                    mention_ids.append(client.cache.place_member_data(data["guild_id"], user_data).id)
                else:
                    mention_ids.append(client.cache.place_user_data(user_data).id)
            data["mention_ids"] = mention_ids

        found_ids = []
        mention_channels = []
        if "mention_channels" in data:
            for channel_data in data["mention_channels"]:
                mention_channels.append(ChannelMention.from_dict(channel_data, client))
                found_ids.append(channel_data["id"])
        if "content" in data:
            for channel_id in channel_mention.findall(data["content"]):
                if channel_id not in found_ids and (channel := client.get_channel(channel_id)):
                    channel_data = {
                        "id": channel.id,
                        "guild_id": channel._guild_id if isinstance(channel, GuildChannel) else None,
                        "type": channel.type,
                        "name": channel.name,
                    }
                    mention_channels.append(ChannelMention.from_dict(channel_data, client))
        if mention_channels:
            data["mention_channels"] = mention_channels

        if "attachments" in data:
            data["attachments"] = Attachment.from_list(data.get("attachments"), client)

        if "embeds" in data:
            data["embeds"] = models.Embed.from_list(data.get("embeds"))

        if "reactions" in data:
            reactions = [
                models.Reaction.from_dict(
                    reaction_data | {"message_id": data["id"], "channel_id": data["channel_id"]},
                    client,
                )
                for reaction_data in data["reactions"]
            ]
            data["reactions"] = reactions

        # TODO: Convert to application object

        if ref_message_data := data.pop("referenced_message", None):
            if not ref_message_data.get("guild_id"):
                ref_message_data["guild_id"] = data.get("guild_id")
            _m = client.cache.place_message_data(ref_message_data)
            data["referenced_message_id"] = _m.id
        elif msg_reference := data.get("message_reference"):
            data["referenced_message_id"] = msg_reference.get("message_id")

        if "interaction_metadata" in data:
            data["interaction_metadata"] = MessageInteractionMetadata.from_dict(data["interaction_metadata"], client)

        if "interaction" in data:
            data["interaction"] = MessageInteraction.from_dict(data["interaction"], client)

        if thread_data := data.pop("thread", None):
            data["thread_channel_id"] = client.cache.place_channel_data(thread_data).id

        if "components" in data:
            components = [
                models.BaseComponent.from_dict_factory(component_data) for component_data in data["components"]
            ]
            data["components"] = components

        if "sticker_items" in data:
            data["sticker_items"] = models.StickerItem.from_list(data["sticker_items"], client)

        return data

    @property
    def system_content(self) -> Optional[str]:
        """Content for system messages. (boosts, welcomes, etc)"""
        match self.type:
            case MessageType.USER_PREMIUM_GUILD_SUBSCRIPTION:
                return f"{self.author.mention} just boosted the server!"
            case MessageType.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1:
                return f"{self.author.mention} just boosted the server! {self.guild.name} has achieved **Level 1!**"
            case MessageType.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2:
                return f"{self.author.mention} just boosted the server! {self.guild.name} has achieved **Level 2!**"
            case MessageType.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3:
                return f"{self.author.mention} just boosted the server! {self.guild.name} has achieved **Level 3!**"
            case MessageType.GUILD_MEMBER_JOIN:
                return GUILD_WELCOME_MESSAGES[
                    int(self.timestamp.timestamp() * 1000)
                    % len(GUILD_WELCOME_MESSAGES)
                    # This is how Discord calculates the welcome message.
                ].format(self.author.mention)
            case MessageType.THREAD_CREATED:
                return f"{self.author.mention} started a thread: {self.thread.mention}. See all **threads**."
            case MessageType.CHANNEL_FOLLOW_ADD:
                return f"{self.author.mention} has added **{self.content}** to this channel. Its most important updates will show up here."
            case MessageType.RECIPIENT_ADD:
                return f"{self.author.mention} added <@{self._mention_ids[0]}> to the thread."
            case MessageType.RECIPIENT_REMOVE:
                return f"{self.author.mention} removed <@{self._mention_ids[0]}> from the thread."
            case MessageType.CHANNEL_NAME_CHANGE:
                return f"{self.author.mention} changed the channel name: **{self.content}**."
            case MessageType.CHANNEL_PINNED_MESSAGE:
                return f"{self.author.mention} pinned a message. See all pinned messages"
            case MessageType.GUILD_DISCOVERY_DISQUALIFIED:
                return "This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details."
            case MessageType.GUILD_DISCOVERY_REQUALIFIED:
                return "This server is eligible for Server Discovery again and has been automatically relisted!"
            case MessageType.GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING:
                return "This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery."
            case MessageType.GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING:
                return "This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery."
            case MessageType.GUILD_INVITE_REMINDER:
                return "**Invite your friends**\nThe best way to setup a server is with your buddies!"
            case MessageType.THREAD_STARTER_MESSAGE:
                if referenced_message := self.get_referenced_message():
                    return referenced_message.content
                return "Sorry, we couldn't load the first message in this thread"
            case MessageType.AUTO_MODERATION_ACTION:
                keyword_matched_content = self.embeds[0].fields[4].value  # The words that triggered the action
                message_content = self.embeds[0].description.replace(
                    keyword_matched_content, f"**{keyword_matched_content}**"
                )
                rule = self.embeds[0].fields[0].value  # What rule was triggered
                channel = self.embeds[0].fields[1].value  # Channel that the action took place in
                return f'AutoMod has blocked a message in <#{channel}>. "{message_content}" from {self.author.mention}. Rule: {rule}.'
            case _:
                return None

    @property
    def jump_url(self) -> str:
        """A url that allows the client to *jump* to this message."""
        return f"https://discord.com/channels/{self._guild_id or '@me'}/{self._channel_id}/{self.id}"

    @property
    def proto_url(self) -> str:
        """A URL like `jump_url` that uses protocols."""
        return f"discord://-/channels/{self._guild_id or '@me'}/{self._channel_id}/{self.id}"

    def answer_voters(
        self, answer_id: int, limit: int = 0, before: Snowflake_Type | None = None
    ) -> PollAnswerVotersIterator:
        """
        An async iterator for getting the voters for an answer in the poll this message has.

        Args:
            answer_id: The answer to get voters for
            after: Get messages after this user ID
            limit: The max number of users to return (default 25, max 100)

        """
        return PollAnswerVotersIterator(self, answer_id, limit, before)

    async def edit(
        self,
        *,
        content: Optional[str] = None,
        embeds: Optional[Union[Sequence[Union["models.Embed", dict]], Union["models.Embed", dict]]] = None,
        embed: Optional[Union["models.Embed", dict]] = None,
        components: Optional[
            Union[
                Sequence[Sequence[Union["models.BaseComponent", dict]]],
                Sequence[Union["models.BaseComponent", dict]],
                "models.BaseComponent",
                dict,
            ]
        ] = None,
        allowed_mentions: Optional[Union[AllowedMentions, dict]] = None,
        attachments: Optional[Optional[Sequence[Union[Attachment, dict]]]] = None,
        files: Optional[Union[UPLOADABLE_TYPE, Sequence[UPLOADABLE_TYPE]]] = None,
        file: Optional[UPLOADABLE_TYPE] = None,
        tts: bool = False,
        flags: Optional[Union[int, MessageFlags]] = None,
        context: "InteractionContext | None" = None,
    ) -> "Message":
        """
        Edits the message.

        Args:
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            allowed_mentions: Allowed mentions for the message.
            attachments: The attachments to keep, only used when editing message.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.
            flags: Message flags to apply.
            context: The interaction context to use for the edit

        Returns:
            New message object with edits applied

        """
        if context:
            return await context.edit(
                self,
                content=content,
                embeds=embeds,
                embed=embed,
                components=components,
                allowed_mentions=allowed_mentions,
                attachments=attachments,
                files=files,
                file=file,
                tts=tts,
            )
        message_payload = process_message_payload(
            content=content,
            embeds=embed if embeds is None else embeds,
            components=components,
            allowed_mentions=allowed_mentions,
            attachments=attachments,
            tts=tts,
            flags=flags,
        )
        if file:
            files = [file, *files] if files else [file]
        message_data = await self._client.http.edit_message(message_payload, self._channel_id, self.id, files=files)
        if message_data:
            return self._client.cache.place_message_data(message_data)

    async def delete(self, delay: int = 0, *, context: "InteractionContext | None" = None) -> None:
        """
        Delete message.

        Args:
            delay: Seconds to wait before deleting message.
            context: An optional interaction context to delete ephemeral messages.

        """

        async def _delete() -> None:
            if delay:
                await asyncio.sleep(delay)

            if MessageFlags.EPHEMERAL in self.flags:
                if not context:
                    raise ValueError("Cannot delete ephemeral message without interaction context parameter")
                await context.delete(self.id)
            else:
                await self._client.http.delete_message(self._channel_id, self.id)

        if delay:
            return asyncio.create_task(_delete())

        return await _delete()

    async def reply(
        self,
        content: Optional[str] = None,
        embeds: Optional[Union[List[Union["models.Embed", dict]], Union["models.Embed", dict]]] = None,
        embed: Optional[Union["models.Embed", dict]] = None,
        **kwargs: Mapping[str, Any],
    ) -> "Message":
        """
        Reply to this message, takes all the same attributes as `send`.

        Args:
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            **kwargs: Additional options to pass to `send`.

        Returns:
            New message object.

        """
        return await self.channel.send(content=content, reply_to=self, embeds=embeds or embed, **kwargs)

    async def create_thread(
        self,
        name: str,
        auto_archive_duration: Union[AutoArchiveDuration, int] = AutoArchiveDuration.ONE_DAY,
        reason: Optional[str] = None,
    ) -> "models.TYPE_THREAD_CHANNEL":
        """
        Create a thread from this message.

        Args:
            name: The name of this thread
            auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
            reason: The optional reason for creating this thread

        Returns:
            The created thread object

        Raises:
            ThreadOutsideOfGuild: if this is invoked on a message outside of a guild

        """
        if self.channel.type not in (ChannelType.GUILD_TEXT, ChannelType.GUILD_NEWS):
            raise ThreadOutsideOfGuild

        thread_data = await self._client.http.create_thread(
            channel_id=self._channel_id,
            name=name,
            auto_archive_duration=auto_archive_duration,
            message_id=self.id,
            reason=reason,
        )
        return self._client.cache.place_channel_data(thread_data)

    async def suppress_embeds(self) -> "Message":
        """
        Suppress embeds for this message.

        !!! note
            Requires the `Permissions.MANAGE_MESSAGES` permission.

        """
        message_data = await self._client.http.edit_message(
            {"flags": MessageFlags.SUPPRESS_EMBEDS}, self._channel_id, self.id
        )
        if message_data:
            return self._client.cache.place_message_data(message_data)

    async def fetch_reaction(
        self,
        emoji: Union["models.PartialEmoji", dict, str],
        limit: Absent[int] = MISSING,
        after: Absent["Snowflake_Type"] = MISSING,
    ) -> List["models.User"]:
        """
        Fetches reactions of a specific emoji from this message.

        Args:
            emoji: The emoji to get
            limit: Max number of users to return (1-100)
            after: Get users after this user ID

        Returns:
            list of users who have reacted with that emoji

        """
        reaction_data = await self._client.http.get_reactions(
            self._channel_id, self.id, process_emoji_req_format(emoji), limit, to_optional_snowflake(after)
        )
        return [self._client.cache.place_user_data(user_data) for user_data in reaction_data]

    async def add_reaction(self, emoji: Union["models.PartialEmoji", dict, str]) -> None:
        """
        Add a reaction to this message.

        Args:
            emoji: the emoji to react with

        """
        emoji = process_emoji_req_format(emoji)
        await self._client.http.create_reaction(self._channel_id, self.id, emoji)

    async def remove_reaction(
        self,
        emoji: Union["models.PartialEmoji", dict, str],
        member: Optional[Union["models.Member", "models.User", "Snowflake_Type"]] = MISSING,
    ) -> None:
        """
        Remove a specific reaction that a user reacted with.

        Args:
            emoji: Emoji to remove
            member: Member to remove reaction of. Default's to ClientUser.

        """
        emoji_str = process_emoji_req_format(emoji)
        if not member:
            member = self._client.user
        user_id = to_snowflake(member)
        if user_id == self._client.user.id:
            await self._client.http.remove_self_reaction(self._channel_id, self.id, emoji_str)
        else:
            await self._client.http.remove_user_reaction(self._channel_id, self.id, emoji_str, user_id)

    async def clear_reactions(self, emoji: Union["models.PartialEmoji", dict, str]) -> None:
        """
        Clear a specific reaction from message.

        Args:
            emoji: The emoji to clear

        """
        emoji = models.process_emoji_req_format(emoji)
        await self._client.http.clear_reaction(self._channel_id, self.id, emoji)

    async def clear_all_reactions(self) -> None:
        """Clear all emojis from a message."""
        await self._client.http.clear_reactions(self._channel_id, self.id)

    async def pin(self) -> None:
        """Pin message."""
        await self._client.http.pin_message(self._channel_id, self.id)
        self.pinned = True

    async def unpin(self) -> None:
        """Unpin message."""
        await self._client.http.unpin_message(self._channel_id, self.id)
        self.pinned = False

    async def publish(self) -> None:
        """
        Publish this message.

        (Discord api calls it "crosspost")

        """
        await self._client.http.crosspost_message(self._channel_id, self.id)

    async def end_poll(self) -> "Message":
        """Ends the poll contained in this message."""
        message_data = await self._client.http.end_poll(self._channel_id, self.id)
        if message_data:
            return self._client.cache.place_message_data(message_data)

activity: Optional[MessageActivity] = attrs.field(repr=False, default=None, converter=optional_c(MessageActivity)) class-attribute

Activity sent with Rich Presence-related chat embeds

application: Optional[models.Application] = attrs.field(repr=False, default=None) class-attribute

Application sent with Rich Presence-related chat embeds

application_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None, converter=to_optional_snowflake) class-attribute

If the message is an Interaction or application-owned webhook, this is the id of the application

attachments: List[Attachment] = attrs.field(repr=False, factory=list) class-attribute

Any attached files

components: Optional[List[models.ActionRow]] = attrs.field(repr=False, default=None) class-attribute

Sent if the message contains components like buttons, action rows, or other interactive components

content: str = attrs.field(repr=False, default=MISSING) class-attribute

Contents of the message

editable: bool property

Whether this message can be edited by the current user

edited_timestamp: Optional[models.Timestamp] = attrs.field(repr=False, default=None, converter=optional_c(timestamp_converter)) class-attribute

When this message was edited (or None if never)

embeds: List[models.Embed] = attrs.field(repr=False, factory=list) class-attribute

Any embedded content

flags: MessageFlags = attrs.field(repr=False, default=MessageFlags.NONE, converter=MessageFlags) class-attribute

Message flags combined as a bitfield

interaction: Optional[MessageInteraction] = attrs.field(repr=False, default=None) class-attribute

(Deprecated in favor of interaction_metadata) Sent if the message is a response to an Interaction

interaction_metadata: Optional[MessageInteractionMetadata] = attrs.field(repr=False, default=None) class-attribute

Sent if the message is a response to an Interaction

jump_url: str property

A url that allows the client to jump to this message.

mention_channels: List[ChannelMention] = attrs.field(repr=False, factory=list) class-attribute

Channels specifically mentioned in this message

mention_everyone: bool = attrs.field(repr=False, default=False) class-attribute

Whether this message mentions everyone

mention_roles: AsyncGenerator[models.Role, None] async property

A generator of roles mentioned in this message

mention_users: AsyncGenerator[Union[models.Member, models.User], None] async property

A generator of users mentioned in this message

message_reference: Optional[MessageReference] = attrs.field(repr=False, default=None, converter=optional_c(MessageReference.from_dict)) class-attribute

Data showing the source of a crosspost, channel follow add, pin, or reply message

nonce: Optional[Union[int, str]] = attrs.field(repr=False, default=None) class-attribute

Used for validating a message was sent

pinned: bool = attrs.field(repr=False, default=False) class-attribute

Whether this message is pinned

poll: Optional[Poll] = attrs.field(repr=False, default=None, converter=optional_c(Poll.from_dict)) class-attribute

A poll.

proto_url: str property

A URL like jump_url that uses protocols.

reactions: List[models.Reaction] = attrs.field(repr=False, factory=list) class-attribute

Reactions to the message

sticker_items: Optional[List[models.StickerItem]] = attrs.field(repr=False, default=None) class-attribute

Sent if the message contains stickers

system_content: Optional[str] property

Content for system messages. (boosts, welcomes, etc)

thread: models.TYPE_THREAD_CHANNEL property

The thread that was started from this message, if any

timestamp: models.Timestamp = attrs.field(repr=False, default=MISSING, converter=optional_c(timestamp_converter)) class-attribute

When this message was sent

tts: bool = attrs.field(repr=False, default=False) class-attribute

Whether this was a TTS message

type: MessageType = attrs.field(repr=False, default=MISSING, converter=optional_c(MessageType)) class-attribute

Type of message

webhook_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None, converter=to_optional_snowflake) class-attribute

If the message is generated by a webhook, this is the webhook's id

add_reaction(emoji) async

Add a reaction to this message.

Parameters:

Name Type Description Default
emoji Union[PartialEmoji, dict, str]

the emoji to react with

required
Source code in interactions/models/discord/message.py
883
884
885
886
887
888
889
890
891
892
async def add_reaction(self, emoji: Union["models.PartialEmoji", dict, str]) -> None:
    """
    Add a reaction to this message.

    Args:
        emoji: the emoji to react with

    """
    emoji = process_emoji_req_format(emoji)
    await self._client.http.create_reaction(self._channel_id, self.id, emoji)

answer_voters(answer_id, limit=0, before=None)

An async iterator for getting the voters for an answer in the poll this message has.

Parameters:

Name Type Description Default
answer_id int

The answer to get voters for

required
after

Get messages after this user ID

required
limit int

The max number of users to return (default 25, max 100)

0
Source code in interactions/models/discord/message.py
681
682
683
684
685
686
687
688
689
690
691
692
693
def answer_voters(
    self, answer_id: int, limit: int = 0, before: Snowflake_Type | None = None
) -> PollAnswerVotersIterator:
    """
    An async iterator for getting the voters for an answer in the poll this message has.

    Args:
        answer_id: The answer to get voters for
        after: Get messages after this user ID
        limit: The max number of users to return (default 25, max 100)

    """
    return PollAnswerVotersIterator(self, answer_id, limit, before)

clear_all_reactions() async

Clear all emojis from a message.

Source code in interactions/models/discord/message.py
927
928
929
async def clear_all_reactions(self) -> None:
    """Clear all emojis from a message."""
    await self._client.http.clear_reactions(self._channel_id, self.id)

clear_reactions(emoji) async

Clear a specific reaction from message.

Parameters:

Name Type Description Default
emoji Union[PartialEmoji, dict, str]

The emoji to clear

required
Source code in interactions/models/discord/message.py
916
917
918
919
920
921
922
923
924
925
async def clear_reactions(self, emoji: Union["models.PartialEmoji", dict, str]) -> None:
    """
    Clear a specific reaction from message.

    Args:
        emoji: The emoji to clear

    """
    emoji = models.process_emoji_req_format(emoji)
    await self._client.http.clear_reaction(self._channel_id, self.id, emoji)

contains_mention(query, *, tag_as_mention=False)

Check whether the message contains the query or not.

Parameters:

Name Type Description Default
query Role

The query to search for

required
tag_as_mention bool

Should BaseUser.tag be checked (only if query is an instance of BaseUser)

False

Returns:

Type Description
bool

A boolean indicating whether the query could be found or not

Source code in interactions/models/discord/message.py
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
def contains_mention(
    self,
    query: "str | re.Pattern[str] | models.BaseUser | models.BaseChannel | models.Role",
    *,
    tag_as_mention: bool = False,
) -> bool:
    """
    Check whether the message contains the query or not.

    Args:
        query: The query to search for
        tag_as_mention: Should `BaseUser.tag` be checked *(only if query is an instance of BaseUser)*

    Returns:
        A boolean indicating whether the query could be found or not

    """
    return mentions(text=self.content or self.system_content, query=query, tag_as_mention=tag_as_mention)

create_thread(name, auto_archive_duration=AutoArchiveDuration.ONE_DAY, reason=None) async

Create a thread from this message.

Parameters:

Name Type Description Default
name str

The name of this thread

required
auto_archive_duration Union[AutoArchiveDuration, int]

duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

AutoArchiveDuration.ONE_DAY
reason Optional[str]

The optional reason for creating this thread

None

Returns:

Type Description
TYPE_THREAD_CHANNEL

The created thread object

Raises:

Type Description
ThreadOutsideOfGuild

if this is invoked on a message outside of a guild

Source code in interactions/models/discord/message.py
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
async def create_thread(
    self,
    name: str,
    auto_archive_duration: Union[AutoArchiveDuration, int] = AutoArchiveDuration.ONE_DAY,
    reason: Optional[str] = None,
) -> "models.TYPE_THREAD_CHANNEL":
    """
    Create a thread from this message.

    Args:
        name: The name of this thread
        auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
        reason: The optional reason for creating this thread

    Returns:
        The created thread object

    Raises:
        ThreadOutsideOfGuild: if this is invoked on a message outside of a guild

    """
    if self.channel.type not in (ChannelType.GUILD_TEXT, ChannelType.GUILD_NEWS):
        raise ThreadOutsideOfGuild

    thread_data = await self._client.http.create_thread(
        channel_id=self._channel_id,
        name=name,
        auto_archive_duration=auto_archive_duration,
        message_id=self.id,
        reason=reason,
    )
    return self._client.cache.place_channel_data(thread_data)

delete(delay=0, *, context=None) async

Delete message.

Parameters:

Name Type Description Default
delay int

Seconds to wait before deleting message.

0
context InteractionContext | None

An optional interaction context to delete ephemeral messages.

None
Source code in interactions/models/discord/message.py
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
async def delete(self, delay: int = 0, *, context: "InteractionContext | None" = None) -> None:
    """
    Delete message.

    Args:
        delay: Seconds to wait before deleting message.
        context: An optional interaction context to delete ephemeral messages.

    """

    async def _delete() -> None:
        if delay:
            await asyncio.sleep(delay)

        if MessageFlags.EPHEMERAL in self.flags:
            if not context:
                raise ValueError("Cannot delete ephemeral message without interaction context parameter")
            await context.delete(self.id)
        else:
            await self._client.http.delete_message(self._channel_id, self.id)

    if delay:
        return asyncio.create_task(_delete())

    return await _delete()

edit(*, content=None, embeds=None, embed=None, components=None, allowed_mentions=None, attachments=None, files=None, file=None, tts=False, flags=None, context=None) async

Edits the message.

Parameters:

Name Type Description Default
content Optional[str]

Message text content.

None
embeds Optional[Union[Sequence[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed Optional[Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[Sequence[Sequence[Union[BaseComponent, dict]]], Sequence[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
attachments Optional[Optional[Sequence[Union[Attachment, dict]]]]

The attachments to keep, only used when editing message.

None
files Optional[Union[UPLOADABLE_TYPE, Sequence[UPLOADABLE_TYPE]]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
file Optional[UPLOADABLE_TYPE]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False
flags Optional[Union[int, MessageFlags]]

Message flags to apply.

None
context InteractionContext | None

The interaction context to use for the edit

None

Returns:

Type Description
Message

New message object with edits applied

Source code in interactions/models/discord/message.py
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
async def edit(
    self,
    *,
    content: Optional[str] = None,
    embeds: Optional[Union[Sequence[Union["models.Embed", dict]], Union["models.Embed", dict]]] = None,
    embed: Optional[Union["models.Embed", dict]] = None,
    components: Optional[
        Union[
            Sequence[Sequence[Union["models.BaseComponent", dict]]],
            Sequence[Union["models.BaseComponent", dict]],
            "models.BaseComponent",
            dict,
        ]
    ] = None,
    allowed_mentions: Optional[Union[AllowedMentions, dict]] = None,
    attachments: Optional[Optional[Sequence[Union[Attachment, dict]]]] = None,
    files: Optional[Union[UPLOADABLE_TYPE, Sequence[UPLOADABLE_TYPE]]] = None,
    file: Optional[UPLOADABLE_TYPE] = None,
    tts: bool = False,
    flags: Optional[Union[int, MessageFlags]] = None,
    context: "InteractionContext | None" = None,
) -> "Message":
    """
    Edits the message.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        allowed_mentions: Allowed mentions for the message.
        attachments: The attachments to keep, only used when editing message.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.
        flags: Message flags to apply.
        context: The interaction context to use for the edit

    Returns:
        New message object with edits applied

    """
    if context:
        return await context.edit(
            self,
            content=content,
            embeds=embeds,
            embed=embed,
            components=components,
            allowed_mentions=allowed_mentions,
            attachments=attachments,
            files=files,
            file=file,
            tts=tts,
        )
    message_payload = process_message_payload(
        content=content,
        embeds=embed if embeds is None else embeds,
        components=components,
        allowed_mentions=allowed_mentions,
        attachments=attachments,
        tts=tts,
        flags=flags,
    )
    if file:
        files = [file, *files] if files else [file]
    message_data = await self._client.http.edit_message(message_payload, self._channel_id, self.id, files=files)
    if message_data:
        return self._client.cache.place_message_data(message_data)

end_poll() async

Ends the poll contained in this message.

Source code in interactions/models/discord/message.py
950
951
952
953
954
async def end_poll(self) -> "Message":
    """Ends the poll contained in this message."""
    message_data = await self._client.http.end_poll(self._channel_id, self.id)
    if message_data:
        return self._client.cache.place_message_data(message_data)

fetch_reaction(emoji, limit=MISSING, after=MISSING) async

Fetches reactions of a specific emoji from this message.

Parameters:

Name Type Description Default
emoji Union[PartialEmoji, dict, str]

The emoji to get

required
limit Absent[int]

Max number of users to return (1-100)

MISSING
after Absent[Snowflake_Type]

Get users after this user ID

MISSING

Returns:

Type Description
List[User]

list of users who have reacted with that emoji

Source code in interactions/models/discord/message.py
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
async def fetch_reaction(
    self,
    emoji: Union["models.PartialEmoji", dict, str],
    limit: Absent[int] = MISSING,
    after: Absent["Snowflake_Type"] = MISSING,
) -> List["models.User"]:
    """
    Fetches reactions of a specific emoji from this message.

    Args:
        emoji: The emoji to get
        limit: Max number of users to return (1-100)
        after: Get users after this user ID

    Returns:
        list of users who have reacted with that emoji

    """
    reaction_data = await self._client.http.get_reactions(
        self._channel_id, self.id, process_emoji_req_format(emoji), limit, to_optional_snowflake(after)
    )
    return [self._client.cache.place_user_data(user_data) for user_data in reaction_data]

fetch_referenced_message(*, force=False) async

Fetch the message this message is referencing, if any.

Parameters:

Name Type Description Default
force bool

Whether to force a fetch from the API

False

Returns:

Type Description
Optional[Message]

The referenced message, if found

Source code in interactions/models/discord/message.py
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
async def fetch_referenced_message(self, *, force: bool = False) -> Optional["Message"]:
    """
    Fetch the message this message is referencing, if any.

    Args:
        force: Whether to force a fetch from the API

    Returns:
        The referenced message, if found

    """
    if self._referenced_message_id is None:
        return None

    try:
        return await self._client.cache.fetch_message(self._channel_id, self._referenced_message_id, force=force)
    except NotFound:
        return None

get_referenced_message()

Get the message this message is referencing, if any.

Returns:

Type Description
Optional[Message]

The referenced message, if found

Source code in interactions/models/discord/message.py
502
503
504
505
506
507
508
509
510
511
512
def get_referenced_message(self) -> Optional["Message"]:
    """
    Get the message this message is referencing, if any.

    Returns:
        The referenced message, if found

    """
    if self._referenced_message_id is None:
        return None
    return self._client.cache.get_message(self._channel_id, self._referenced_message_id)

pin() async

Pin message.

Source code in interactions/models/discord/message.py
931
932
933
934
async def pin(self) -> None:
    """Pin message."""
    await self._client.http.pin_message(self._channel_id, self.id)
    self.pinned = True

publish() async

Publish this message.

(Discord api calls it "crosspost")

Source code in interactions/models/discord/message.py
941
942
943
944
945
946
947
948
async def publish(self) -> None:
    """
    Publish this message.

    (Discord api calls it "crosspost")

    """
    await self._client.http.crosspost_message(self._channel_id, self.id)

remove_reaction(emoji, member=MISSING) async

Remove a specific reaction that a user reacted with.

Parameters:

Name Type Description Default
emoji Union[PartialEmoji, dict, str]

Emoji to remove

required
member Optional[Union[Member, User, Snowflake_Type]]

Member to remove reaction of. Default's to ClientUser.

MISSING
Source code in interactions/models/discord/message.py
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
async def remove_reaction(
    self,
    emoji: Union["models.PartialEmoji", dict, str],
    member: Optional[Union["models.Member", "models.User", "Snowflake_Type"]] = MISSING,
) -> None:
    """
    Remove a specific reaction that a user reacted with.

    Args:
        emoji: Emoji to remove
        member: Member to remove reaction of. Default's to ClientUser.

    """
    emoji_str = process_emoji_req_format(emoji)
    if not member:
        member = self._client.user
    user_id = to_snowflake(member)
    if user_id == self._client.user.id:
        await self._client.http.remove_self_reaction(self._channel_id, self.id, emoji_str)
    else:
        await self._client.http.remove_user_reaction(self._channel_id, self.id, emoji_str, user_id)

reply(content=None, embeds=None, embed=None, **kwargs) async

Reply to this message, takes all the same attributes as send.

Parameters:

Name Type Description Default
content Optional[str]

Message text content.

None
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed Optional[Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
**kwargs Mapping[str, Any]

Additional options to pass to send.

{}

Returns:

Type Description
Message

New message object.

Source code in interactions/models/discord/message.py
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
async def reply(
    self,
    content: Optional[str] = None,
    embeds: Optional[Union[List[Union["models.Embed", dict]], Union["models.Embed", dict]]] = None,
    embed: Optional[Union["models.Embed", dict]] = None,
    **kwargs: Mapping[str, Any],
) -> "Message":
    """
    Reply to this message, takes all the same attributes as `send`.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        **kwargs: Additional options to pass to `send`.

    Returns:
        New message object.

    """
    return await self.channel.send(content=content, reply_to=self, embeds=embeds or embed, **kwargs)

suppress_embeds() async

Suppress embeds for this message.

Note

Requires the Permissions.MANAGE_MESSAGES permission.

Source code in interactions/models/discord/message.py
846
847
848
849
850
851
852
853
854
855
856
857
858
async def suppress_embeds(self) -> "Message":
    """
    Suppress embeds for this message.

    !!! note
        Requires the `Permissions.MANAGE_MESSAGES` permission.

    """
    message_data = await self._client.http.edit_message(
        {"flags": MessageFlags.SUPPRESS_EMBEDS}, self._channel_id, self.id
    )
    if message_data:
        return self._client.cache.place_message_data(message_data)

unpin() async

Unpin message.

Source code in interactions/models/discord/message.py
936
937
938
939
async def unpin(self) -> None:
    """Unpin message."""
    await self._client.http.unpin_message(self._channel_id, self.id)
    self.pinned = False

MessageActivity dataclass

Source code in interactions/models/discord/message.py
165
166
167
168
169
170
@dataclass
class MessageActivity:
    type: MessageActivityType
    """type of message activity"""
    party_id: str = None
    """party_id from a Rich Presence event"""

party_id: str = None class-attribute

party_id from a Rich Presence event

type: MessageActivityType class-attribute

type of message activity

MessageInteraction

Bases: DiscordObject

Source code in interactions/models/discord/message.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class MessageInteraction(DiscordObject):
    type: InteractionType = attrs.field(repr=False, converter=InteractionType)
    """the type of interaction"""
    name: str = attrs.field(
        repr=False,
    )
    """the name of the application command"""

    _user_id: "Snowflake_Type" = attrs.field(
        repr=False,
    )
    """ID of the user who triggered the interaction"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        user_data = data["user"]
        data["user_id"] = client.cache.place_user_data(user_data).id
        return data

    @property
    def user(self) -> "models.User":
        """Get the user associated with this interaction."""
        return self.client.get_user(self._user_id)

name: str = attrs.field(repr=False) class-attribute

the name of the application command

type: InteractionType = attrs.field(repr=False, converter=InteractionType) class-attribute

the type of interaction

user: models.User property

Get the user associated with this interaction.

MessageInteractionMetadata

Bases: DiscordObject

Source code in interactions/models/discord/message.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class MessageInteractionMetadata(DiscordObject):
    type: InteractionType = attrs.field(repr=False, converter=InteractionType)
    """The type of interaction"""
    authorizing_integration_owners: dict[IntegrationType, Snowflake] = attrs.field(repr=False, factory=dict)
    """IDs for installation context(s) related to an interaction."""
    original_response_message_id: "Optional[Snowflake_Type]" = attrs.field(
        repr=False, default=None, converter=to_optional_snowflake
    )
    """ID of the original response message, present only on follow-up messages"""
    interacted_message_id: "Optional[Snowflake_Type]" = attrs.field(
        repr=False, default=None, converter=to_optional_snowflake
    )
    """ID of the message that contained interactive component, present only on messages created from component interactions"""
    triggering_interaction_metadata: "Optional[MessageInteractionMetadata]" = attrs.field(repr=False, default=None)
    """Metadata for the interaction that was used to open the modal, present only on modal submit interactions"""

    _user_id: "Snowflake_Type" = attrs.field(
        repr=False,
    )
    """ID of the user who triggered the interaction"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if "authorizing_integration_owners" in data:
            data["authorizing_integration_owners"] = {
                IntegrationType(int(integration_type)): Snowflake(owner_id)
                for integration_type, owner_id in data["authorizing_integration_owners"].items()
            }
        if "triggering_interaction_metadata" in data:
            data["triggering_interaction_metadata"] = cls.from_dict(data["triggering_interaction_metadata"], client)

        user_data = data["user"]
        data["user_id"] = client.cache.place_user_data(user_data).id

        return data

    @property
    def user(self) -> "models.User":
        """Get the user associated with this interaction."""
        return self.client.get_user(self._user_id)

authorizing_integration_owners: dict[IntegrationType, Snowflake] = attrs.field(repr=False, factory=dict) class-attribute

IDs for installation context(s) related to an interaction.

interacted_message_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None, converter=to_optional_snowflake) class-attribute

ID of the message that contained interactive component, present only on messages created from component interactions

original_response_message_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None, converter=to_optional_snowflake) class-attribute

ID of the original response message, present only on follow-up messages

triggering_interaction_metadata: Optional[MessageInteractionMetadata] = attrs.field(repr=False, default=None) class-attribute

Metadata for the interaction that was used to open the modal, present only on modal submit interactions

type: InteractionType = attrs.field(repr=False, converter=InteractionType) class-attribute

The type of interaction

user: models.User property

Get the user associated with this interaction.

MessageReference

Bases: DictSerializationMixin

Reference to an originating message.

Can be used for replies.

Source code in interactions/models/discord/message.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class MessageReference(DictSerializationMixin):
    """
    Reference to an originating message.

    Can be used for replies.

    """

    message_id: int = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake))
    """id of the originating message."""
    channel_id: Optional[int] = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake))
    """id of the originating message's channel."""
    guild_id: Optional[int] = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake))
    """id of the originating message's guild."""
    fail_if_not_exists: bool = attrs.field(repr=False, default=True)
    """When sending a message, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true."""

    @classmethod
    def for_message(cls, message: "Message", fail_if_not_exists: bool = True) -> "MessageReference":
        """
        Creates a reference to a message.

        Parameters
            message: The target message to reference.
            fail_if_not_exists: Whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message

        Returns:
            A MessageReference object.

        """
        return cls(
            message_id=message.id,
            channel_id=message._channel_id,
            guild_id=message._guild_id,
            fail_if_not_exists=fail_if_not_exists,
        )

channel_id: Optional[int] = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake)) class-attribute

id of the originating message's channel.

fail_if_not_exists: bool = attrs.field(repr=False, default=True) class-attribute

When sending a message, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true.

guild_id: Optional[int] = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake)) class-attribute

id of the originating message's guild.

message_id: int = attrs.field(repr=False, default=None, converter=optional_c(to_snowflake)) class-attribute

id of the originating message.

for_message(message, fail_if_not_exists=True) classmethod

Creates a reference to a message.

Parameters message: The target message to reference. fail_if_not_exists: Whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message

Returns:

Type Description
MessageReference

A MessageReference object.

Source code in interactions/models/discord/message.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
@classmethod
def for_message(cls, message: "Message", fail_if_not_exists: bool = True) -> "MessageReference":
    """
    Creates a reference to a message.

    Parameters
        message: The target message to reference.
        fail_if_not_exists: Whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message

    Returns:
        A MessageReference object.

    """
    return cls(
        message_id=message.id,
        channel_id=message._channel_id,
        guild_id=message._guild_id,
        fail_if_not_exists=fail_if_not_exists,
    )

MessageType

Bases: CursedIntEnum

Types of message.

Ref: https://discord.com/developers/docs/resources/channel#message-object-message-types

Source code in interactions/models/discord/enums.py
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
class MessageType(CursedIntEnum):
    """
    Types of message.

    Ref: https://discord.com/developers/docs/resources/channel#message-object-message-types
    """

    DEFAULT = 0
    RECIPIENT_ADD = 1
    RECIPIENT_REMOVE = 2
    CALL = 3
    CHANNEL_NAME_CHANGE = 4
    CHANNEL_ICON_CHANGE = 5
    CHANNEL_PINNED_MESSAGE = 6
    GUILD_MEMBER_JOIN = 7
    USER_PREMIUM_GUILD_SUBSCRIPTION = 8
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11
    CHANNEL_FOLLOW_ADD = 12
    GUILD_DISCOVERY_DISQUALIFIED = 14
    GUILD_DISCOVERY_REQUALIFIED = 15
    GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16
    GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17
    THREAD_CREATED = 18
    REPLY = 19
    APPLICATION_COMMAND = 20
    THREAD_STARTER_MESSAGE = 21
    GUILD_INVITE_REMINDER = 22
    CONTEXT_MENU_COMMAND = 23
    AUTO_MODERATION_ACTION = 24
    ROLE_SUBSCRIPTION_PURCHASE = 25
    INTERACTION_PREMIUM_UPSELL = 26
    STAGE_START = 27
    STAGE_END = 28
    STAGE_SPEAKER = 29
    STAGE_TOPIC = 31
    GUILD_APPLICATION_PREMIUM_SUBSCRIPTION = 32
    GUILD_INCIDENT_ALERT_MODE_ENABLED = 36
    GUILD_INCIDENT_ALERT_MODE_DISABLED = 37
    GUILD_INCIDENT_REPORT_RAID = 38
    GUILD_INCIDENT_REPORT_FALSE_ALARM = 39
    PURCHASE_NOTIFICATION = 44

    @classmethod
    def deletable(cls) -> Tuple["MessageType", ...]:
        """Return a tuple of message types that can be deleted."""
        return (
            cls.DEFAULT,
            cls.CHANNEL_PINNED_MESSAGE,
            cls.GUILD_MEMBER_JOIN,
            cls.USER_PREMIUM_GUILD_SUBSCRIPTION,
            cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1,
            cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2,
            cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3,
            cls.CHANNEL_FOLLOW_ADD,
            cls.GUILD_DISCOVERY_DISQUALIFIED,
            cls.GUILD_DISCOVERY_REQUALIFIED,
            cls.GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING,
            cls.GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING,
            cls.THREAD_CREATED,
            cls.REPLY,
            cls.APPLICATION_COMMAND,
            cls.GUILD_INVITE_REMINDER,
            cls.CONTEXT_MENU_COMMAND,
            cls.AUTO_MODERATION_ACTION,
            cls.ROLE_SUBSCRIPTION_PURCHASE,
            cls.INTERACTION_PREMIUM_UPSELL,
            cls.STAGE_START,
            cls.STAGE_END,
            cls.STAGE_SPEAKER,
            cls.STAGE_TOPIC,
            cls.GUILD_APPLICATION_PREMIUM_SUBSCRIPTION,
            cls.GUILD_INCIDENT_ALERT_MODE_ENABLED,
            cls.GUILD_INCIDENT_ALERT_MODE_DISABLED,
            cls.GUILD_INCIDENT_REPORT_RAID,
            cls.GUILD_INCIDENT_REPORT_FALSE_ALARM,
            cls.PURCHASE_NOTIFICATION,
        )

deletable() classmethod

Return a tuple of message types that can be deleted.

Source code in interactions/models/discord/enums.py
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
@classmethod
def deletable(cls) -> Tuple["MessageType", ...]:
    """Return a tuple of message types that can be deleted."""
    return (
        cls.DEFAULT,
        cls.CHANNEL_PINNED_MESSAGE,
        cls.GUILD_MEMBER_JOIN,
        cls.USER_PREMIUM_GUILD_SUBSCRIPTION,
        cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1,
        cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2,
        cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3,
        cls.CHANNEL_FOLLOW_ADD,
        cls.GUILD_DISCOVERY_DISQUALIFIED,
        cls.GUILD_DISCOVERY_REQUALIFIED,
        cls.GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING,
        cls.GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING,
        cls.THREAD_CREATED,
        cls.REPLY,
        cls.APPLICATION_COMMAND,
        cls.GUILD_INVITE_REMINDER,
        cls.CONTEXT_MENU_COMMAND,
        cls.AUTO_MODERATION_ACTION,
        cls.ROLE_SUBSCRIPTION_PURCHASE,
        cls.INTERACTION_PREMIUM_UPSELL,
        cls.STAGE_START,
        cls.STAGE_END,
        cls.STAGE_SPEAKER,
        cls.STAGE_TOPIC,
        cls.GUILD_APPLICATION_PREMIUM_SUBSCRIPTION,
        cls.GUILD_INCIDENT_ALERT_MODE_ENABLED,
        cls.GUILD_INCIDENT_ALERT_MODE_DISABLED,
        cls.GUILD_INCIDENT_REPORT_RAID,
        cls.GUILD_INCIDENT_REPORT_FALSE_ALARM,
        cls.PURCHASE_NOTIFICATION,
    )

process_allowed_mentions(allowed_mentions)

Process allowed mentions into a dictionary.

Parameters:

Name Type Description Default
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions object or dictionary

required

Returns:

Type Description
Optional[dict]

Dictionary of allowed mentions

Raises:

Type Description
ValueError

Invalid allowed mentions

Source code in interactions/models/discord/message.py
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
def process_allowed_mentions(allowed_mentions: Optional[Union[AllowedMentions, dict]]) -> Optional[dict]:
    """
    Process allowed mentions into a dictionary.

    Args:
        allowed_mentions: Allowed mentions object or dictionary

    Returns:
        Dictionary of allowed mentions

    Raises:
        ValueError: Invalid allowed mentions

    """
    if not allowed_mentions:
        return allowed_mentions

    if isinstance(allowed_mentions, dict):
        return allowed_mentions

    if isinstance(allowed_mentions, AllowedMentions):
        return allowed_mentions.to_dict()

    raise ValueError(f"Invalid allowed mentions: {allowed_mentions}")

process_message_payload(content=None, embeds=None, components=None, stickers=None, allowed_mentions=None, reply_to=None, attachments=None, tts=False, flags=None, nonce=None, enforce_nonce=False, poll=None, **kwargs)

Format message content for it to be ready to send discord.

Parameters:

Name Type Description Default
content Optional[str]

Message text content.

None
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
stickers Optional[Union[List[Union[Sticker, Snowflake_Type]], Sticker, Snowflake_Type]]

IDs of up to 3 stickers in the server to send in the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
reply_to Optional[Union[MessageReference, Message, dict, Snowflake_Type]]

Message to reference, must be from the same channel.

None
attachments Optional[List[Union[Attachment, dict]]]

The attachments to keep, only used when editing message.

None
tts bool

Should this message use Text To Speech.

False
flags Optional[Union[int, MessageFlags]]

Message flags to apply.

None
nonce Optional[str | int]

Used to verify a message was sent.

None
enforce_nonce bool

If enabled and nonce is present, it will be checked for uniqueness in the past few minutes. If another message was created by the same author with the same nonce, that message will be returned and no new message will be created.

False
poll Optional[Poll | dict]

A poll.

None

Returns:

Type Description
dict

Dictionary

Source code in interactions/models/discord/message.py
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
def process_message_payload(
    content: Optional[str] = None,
    embeds: Optional[Union[List[Union["models.Embed", dict]], Union["models.Embed", dict]]] = None,
    components: Optional[
        Union[
            List[List[Union["models.BaseComponent", dict]]],
            List[Union["models.BaseComponent", dict]],
            "models.BaseComponent",
            dict,
        ]
    ] = None,
    stickers: Optional[
        Union[List[Union["models.Sticker", "Snowflake_Type"]], "models.Sticker", "Snowflake_Type"]
    ] = None,
    allowed_mentions: Optional[Union[AllowedMentions, dict]] = None,
    reply_to: Optional[Union[MessageReference, Message, dict, "Snowflake_Type"]] = None,
    attachments: Optional[List[Union[Attachment, dict]]] = None,
    tts: bool = False,
    flags: Optional[Union[int, MessageFlags]] = None,
    nonce: Optional[str | int] = None,
    enforce_nonce: bool = False,
    poll: Optional[Poll | dict] = None,
    **kwargs,
) -> dict:
    """
    Format message content for it to be ready to send discord.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        stickers: IDs of up to 3 stickers in the server to send in the message.
        allowed_mentions: Allowed mentions for the message.
        reply_to: Message to reference, must be from the same channel.
        attachments: The attachments to keep, only used when editing message.
        tts: Should this message use Text To Speech.
        flags: Message flags to apply.
        nonce: Used to verify a message was sent.
        enforce_nonce: If enabled and nonce is present, it will be checked for uniqueness in the past few minutes. \
            If another message was created by the same author with the same nonce, that message will be returned \
            and no new message will be created.
        poll: A poll.

    Returns:
        Dictionary

    """
    embeds = process_embeds(embeds)
    if isinstance(embeds, list):
        embeds = embeds if all(e is not None for e in embeds) else None

    components = models.process_components(components)
    if stickers:
        stickers = [to_snowflake(sticker) for sticker in stickers]
    allowed_mentions = process_allowed_mentions(allowed_mentions)
    message_reference = process_message_reference(reply_to)
    if attachments:
        attachments = [attachment.to_dict() for attachment in attachments]

    if isinstance(poll, Poll):
        poll = poll.to_dict()

    return dict_filter_none(
        {
            "content": content,
            "embeds": embeds,
            "components": components,
            "sticker_ids": stickers,
            "allowed_mentions": allowed_mentions,
            "message_reference": message_reference,
            "attachments": attachments,
            "tts": tts,
            "flags": flags,
            "nonce": nonce,
            "enforce_nonce": enforce_nonce,
            "poll": poll,
            **kwargs,
        }
    )

process_message_reference(message_reference)

Process mention references into a dictionary.

Parameters:

Name Type Description Default
message_reference Optional[Union[MessageReference, Message, dict, Snowflake_Type]]

Message reference object

required

Returns:

Type Description
Optional[dict]

Message reference dictionary

Raises:

Type Description
ValueError

Invalid message reference

Source code in interactions/models/discord/message.py
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
def process_message_reference(
    message_reference: Optional[Union[MessageReference, Message, dict, "Snowflake_Type"]]
) -> Optional[dict]:
    """
    Process mention references into a dictionary.

    Args:
        message_reference: Message reference object

    Returns:
        Message reference dictionary

    Raises:
        ValueError: Invalid message reference

    """
    if not message_reference:
        return message_reference

    if isinstance(message_reference, dict):
        return message_reference

    if isinstance(message_reference, (str, int)):
        message_reference = MessageReference(message_id=message_reference)

    if isinstance(message_reference, Message):
        message_reference = MessageReference.for_message(message_reference)

    if isinstance(message_reference, MessageReference):
        return message_reference.to_dict()

    raise ValueError(f"Invalid message reference: {message_reference}")

Reaction

Bases: ClientObject

Source code in interactions/models/discord/reaction.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class Reaction(ClientObject):
    count: int = attrs.field(
        repr=False,
    )
    """times this emoji has been used to react"""
    me: bool = attrs.field(repr=False, default=False)
    """whether the current user reacted using this emoji"""
    emoji: "PartialEmoji" = attrs.field(repr=False, converter=PartialEmoji.from_dict)
    """emoji information"""

    _channel_id: "Snowflake_Type" = attrs.field(repr=False, converter=to_snowflake)
    _message_id: "Snowflake_Type" = attrs.field(repr=False, converter=to_snowflake)

    def users(self, limit: int = 0, after: "Snowflake_Type" = None) -> ReactionUsers:
        """Users who reacted using this emoji."""
        return ReactionUsers(self, limit, after)

    @property
    def message(self) -> "Message":
        """The message this reaction is on."""
        return self._client.cache.get_message(self._channel_id, self._message_id)

    @property
    def channel(self) -> "TYPE_ALL_CHANNEL":
        """The channel this reaction is on."""
        return self._client.cache.get_channel(self._channel_id)

    async def remove(self) -> None:
        """Remove all this emoji's reactions from the message."""
        await self._client.http.clear_reaction(self._channel_id, self._message_id, self.emoji.req_format)

channel: TYPE_ALL_CHANNEL property

The channel this reaction is on.

count: int = attrs.field(repr=False) class-attribute

times this emoji has been used to react

emoji: PartialEmoji = attrs.field(repr=False, converter=PartialEmoji.from_dict) class-attribute

emoji information

me: bool = attrs.field(repr=False, default=False) class-attribute

whether the current user reacted using this emoji

message: Message property

The message this reaction is on.

remove() async

Remove all this emoji's reactions from the message.

Source code in interactions/models/discord/reaction.py
95
96
97
async def remove(self) -> None:
    """Remove all this emoji's reactions from the message."""
    await self._client.http.clear_reaction(self._channel_id, self._message_id, self.emoji.req_format)

users(limit=0, after=None)

Users who reacted using this emoji.

Source code in interactions/models/discord/reaction.py
81
82
83
def users(self, limit: int = 0, after: "Snowflake_Type" = None) -> ReactionUsers:
    """Users who reacted using this emoji."""
    return ReactionUsers(self, limit, after)

ReactionUsers

Bases: AsyncIterator

An async iterator for searching through a channel's history.

Attributes:

Name Type Description
reaction Reaction

The reaction to search through

limit Reaction

The maximum number of users to return (set to 0 for no limit)

after Snowflake_Type

get users after this message ID

Source code in interactions/models/discord/reaction.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class ReactionUsers(AsyncIterator):
    """
    An async iterator for searching through a channel's history.

    Attributes:
        reaction: The reaction to search through
        limit: The maximum number of users to return (set to 0 for no limit)
        after: get users after this message ID

    """

    def __init__(self, reaction: "Reaction", limit: int = 50, after: Optional["Snowflake_Type"] = None) -> None:
        self.reaction: "Reaction" = reaction
        self.after: "Snowflake_Type" = after
        self._more = True
        super().__init__(limit)

    async def fetch(self) -> List["User"]:
        """
        Gets all the users who reacted to the message. Requests user data from discord API if not cached.

        Returns:
            A list of users who reacted to the message.

        """
        if self._more:
            expected = self.get_limit

            if self.after and not self.last:
                self.last = namedtuple("temp", "id")
                self.last.id = self.after

            users = await self.reaction._client.http.get_reactions(
                self.reaction._channel_id,
                self.reaction._message_id,
                self.reaction.emoji.req_format,
                limit=expected,
                after=self.last.id or MISSING,
            )
            if not users:
                raise QueueEmpty
            self._more = len(users) == expected
            return [self.reaction._client.cache.place_user_data(u) for u in users]
        raise QueueEmpty

fetch() async

Gets all the users who reacted to the message. Requests user data from discord API if not cached.

Returns:

Type Description
List[User]

A list of users who reacted to the message.

Source code in interactions/models/discord/reaction.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
async def fetch(self) -> List["User"]:
    """
    Gets all the users who reacted to the message. Requests user data from discord API if not cached.

    Returns:
        A list of users who reacted to the message.

    """
    if self._more:
        expected = self.get_limit

        if self.after and not self.last:
            self.last = namedtuple("temp", "id")
            self.last.id = self.after

        users = await self.reaction._client.http.get_reactions(
            self.reaction._channel_id,
            self.reaction._message_id,
            self.reaction.emoji.req_format,
            limit=expected,
            after=self.last.id or MISSING,
        )
        if not users:
            raise QueueEmpty
        self._more = len(users) == expected
        return [self.reaction._client.cache.place_user_data(u) for u in users]
    raise QueueEmpty

UX.

Embed

Bases: DictSerializationMixin

Represents a discord embed object.

Source code in interactions/models/discord/embed.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class Embed(DictSerializationMixin):
    """Represents a discord embed object."""

    title: Optional[str] = attrs.field(default=None, repr=True)
    """The title of the embed"""
    description: Optional[str] = attrs.field(default=None, repr=True)
    """The description of the embed"""
    color: Optional[Union[Color, dict, tuple, list, str, int]] = attrs.field(
        default=None, repr=True, metadata=export_converter(process_color)
    )
    """The colour of the embed"""
    url: Optional[str] = attrs.field(default=None, validator=v_optional(instance_of(str)), repr=True)
    """The url the embed should direct to when clicked"""
    timestamp: Optional[Timestamp] = attrs.field(
        default=None,
        converter=c_optional(timestamp_converter),
        validator=v_optional(instance_of((datetime, float, int))),
        repr=True,
    )
    """Timestamp of embed content"""
    fields: List[EmbedField] = attrs.field(factory=list, converter=EmbedField.from_list, repr=True)
    """A list of [fields][interactions.models.discord.embed.EmbedField] to go in the embed"""
    author: Optional[EmbedAuthor] = attrs.field(repr=False, default=None, converter=c_optional(EmbedAuthor.from_dict))
    """The author of the embed"""
    thumbnail: Optional[EmbedAttachment] = attrs.field(
        repr=False, default=None, converter=c_optional(EmbedAttachment.from_dict)
    )
    """The thumbnail of the embed"""
    images: list[EmbedAttachment] = attrs.field(
        repr=False, factory=list, converter=list_converter(EmbedAttachment.from_dict)
    )
    """The images of the embed"""
    video: Optional[EmbedAttachment] = attrs.field(
        repr=False,
        default=None,
        converter=c_optional(EmbedAttachment.from_dict),
        metadata=no_export_meta,
    )
    """The video of the embed, only used by system embeds"""
    footer: Optional[EmbedFooter] = attrs.field(repr=False, default=None, converter=c_optional(EmbedFooter.converter))
    """The footer of the embed"""
    provider: Optional[EmbedProvider] = attrs.field(
        repr=False,
        default=None,
        converter=c_optional(EmbedProvider.from_dict),
        metadata=no_export_meta,
    )
    """The provider of the embed, only used for system embeds"""
    type: EmbedType = attrs.field(
        repr=False,
        default=EmbedType.RICH,
        converter=c_optional(EmbedType),
        metadata=no_export_meta,
    )

    @classmethod
    def _process_dict(cls, data: Dict[str, Any]) -> Dict[str, Any]:
        if image_data := data.pop("image", None):
            data["images"] = [image_data]
        return data

    @property
    def image(self) -> Optional[EmbedAttachment]:
        """
        The image of the embed.

        Raises:
            ValueError: If there are multiple images in the embed.

        """
        if len(self.images) <= 1:
            return self.images[0] if self.images else None
        raise ValueError("There are multiple images in this embed, use `images` instead")

    @image.setter
    def image(self, value: Optional[EmbedAttachment]) -> "Embed":
        """Set the image of the embed."""
        self.images = [] if value is None else [value]
        return self

    @title.validator
    def _name_validation(self, attribute: str, value: Any) -> None:
        """Validate the embed title."""
        if value is not None:
            if isinstance(value, str):
                if len(value) > EMBED_MAX_NAME_LENGTH:
                    raise ValueError(f"Title cannot exceed {EMBED_MAX_NAME_LENGTH} characters")
                return
            raise TypeError("Title must be of type String")

    @description.validator
    def _description_validation(self, attribute: str, value: Any) -> None:
        """Validate the description."""
        if value is not None:
            if isinstance(value, str):
                if len(value) > EMBED_MAX_DESC_LENGTH:
                    raise ValueError(f"Description cannot exceed {EMBED_MAX_DESC_LENGTH} characters")
                return
            raise TypeError("Description must be of type String")

    @fields.validator
    def _fields_validation(self, attribute: str, value: Any) -> None:
        """Validate the fields."""
        if isinstance(value, list) and len(value) > EMBED_MAX_FIELDS:
            raise ValueError(f"Embeds can only hold {EMBED_MAX_FIELDS} fields")

    def _check_object(self) -> None:
        self._name_validation("title", self.title)
        self._description_validation("description", self.description)
        self._fields_validation("fields", self.fields)

        if len(self) > EMBED_TOTAL_MAX:
            raise ValueError(
                "Your embed is too large, more info at https://discord.com/developers/docs/resources/channel#embed-limits"
            )

    def __len__(self) -> int:
        # yes i know there are far more optimal ways to write this
        # its written like this for readability
        total: int = 0
        if self.title:
            total += len(self.title)
        if self.description:
            total += len(self.description)
        if self.footer:
            total += len(self.footer)
        if self.author:
            total += len(self.author)
        if self.fields:
            total += sum(map(len, self.fields))
        return total

    def __bool__(self) -> bool:
        return any(
            (
                self.title,
                self.description,
                self.fields,
                self.author,
                self.thumbnail,
                self.footer,
                self.images,
                self.video,
            )
        )

    def set_author(
        self,
        name: str,
        url: Optional[str] = None,
        icon_url: Optional[str] = None,
    ) -> "Embed":
        """
        Set the author field of the embed.

        Args:
            name: The text to go in the title section
            url: A url link to the author
            icon_url: A url of an image to use as the icon

        """
        self.author = EmbedAuthor(name=name, url=url, icon_url=icon_url)
        return self

    def set_thumbnail(self, url: str) -> "Embed":
        """
        Set the thumbnail of the embed.

        Args:
            url: the url of the image to use

        """
        self.thumbnail = EmbedAttachment(url=url)
        return self

    def set_image(self, url: str) -> "Embed":
        """
        Set the image of the embed.

        Args:
            url: the url of the image to use

        """
        self.images = [EmbedAttachment(url=url)]
        return self

    def set_images(self, *images: str) -> "Embed":
        """
        Set multiple images for the embed.

        Note:
            To use multiple images, you must also set a url for this embed.

        Warning:
            This takes advantage of an undocumented feature of the API, and may be removed at any time.

        Args:
            images: the images to use

        """
        if len(self.images) + len(images) > 1 and not self.url:
            raise ValueError("To use multiple images, you must also set a url for this embed")

        self.images = [EmbedAttachment(url=url) for url in images]
        return self

    def add_image(self, image: str) -> "Embed":
        """
        Add an image to the embed.

        Note:
            To use multiple images, you must also set a url for this embed.

        Warning:
            This takes advantage of an undocumented feature of the API, and may be removed at any time.

        Args:
            image: the image to add

        """
        if len(self.images) > 0 and not self.url:
            raise ValueError("To use multiple images, you must also set a url for this embed")
        self.images.append(EmbedAttachment(url=image))
        return self

    def set_footer(self, text: str, icon_url: Optional[str] = None) -> "Embed":
        """
        Set the footer field of the embed.

        Args:
            text: The text to go in the title section
            icon_url: A url of an image to use as the icon

        """
        self.footer = EmbedFooter(text=text, icon_url=icon_url)
        return self

    def add_field(self, name: str, value: Any, inline: bool = False) -> "Embed":
        """
        Add a field to the embed.

        Args:
            name: The title of this field
            value: The value in this field
            inline: Should this field be inline with other fields?

        """
        self.fields.append(EmbedField(name, str(value), inline))
        self._fields_validation("fields", self.fields)

        return self

    def add_fields(self, *fields: EmbedField | str | dict) -> "Embed":
        """
        Add multiple fields to the embed.

        Args:
            fields: The fields to add

        """
        for _field in fields:
            if isinstance(_field, EmbedField):
                self.fields.append(_field)
                self._fields_validation("fields", self.fields)
            elif isinstance(_field, str):
                self.add_field(_field, _field)
            elif isinstance(_field, dict):
                self.add_field(**_field)
            else:
                raise TypeError(f"Expected EmbedField, str or dict, got {type(_field).__name__}")
        return self

    def to_dict(self) -> Dict[str, Any]:
        data = super().to_dict()
        if images := data.pop("images", []):
            if len(images) > 1:
                if not self.url:
                    raise ValueError("To use multiple images, you must also set a url for this embed")

                data["image"] = images[0]
                data = [data]

                data.extend({"image": image, "url": self.url} for image in images[1:])
            else:
                data["image"] = images[0]

        return data

author: Optional[EmbedAuthor] = attrs.field(repr=False, default=None, converter=c_optional(EmbedAuthor.from_dict)) class-attribute

The author of the embed

color: Optional[Union[Color, dict, tuple, list, str, int]] = attrs.field(default=None, repr=True, metadata=export_converter(process_color)) class-attribute

The colour of the embed

description: Optional[str] = attrs.field(default=None, repr=True) class-attribute

The description of the embed

fields: List[EmbedField] = attrs.field(factory=list, converter=EmbedField.from_list, repr=True) class-attribute

A list of fields to go in the embed

footer: Optional[EmbedFooter] = attrs.field(repr=False, default=None, converter=c_optional(EmbedFooter.converter)) class-attribute

The footer of the embed

image: Optional[EmbedAttachment] writable property

The image of the embed.

Raises:

Type Description
ValueError

If there are multiple images in the embed.

images: list[EmbedAttachment] = attrs.field(repr=False, factory=list, converter=list_converter(EmbedAttachment.from_dict)) class-attribute

The images of the embed

provider: Optional[EmbedProvider] = attrs.field(repr=False, default=None, converter=c_optional(EmbedProvider.from_dict), metadata=no_export_meta) class-attribute

The provider of the embed, only used for system embeds

thumbnail: Optional[EmbedAttachment] = attrs.field(repr=False, default=None, converter=c_optional(EmbedAttachment.from_dict)) class-attribute

The thumbnail of the embed

timestamp: Optional[Timestamp] = attrs.field(default=None, converter=c_optional(timestamp_converter), validator=v_optional(instance_of((datetime, float, int))), repr=True) class-attribute

Timestamp of embed content

title: Optional[str] = attrs.field(default=None, repr=True) class-attribute

The title of the embed

url: Optional[str] = attrs.field(default=None, validator=v_optional(instance_of(str)), repr=True) class-attribute

The url the embed should direct to when clicked

video: Optional[EmbedAttachment] = attrs.field(repr=False, default=None, converter=c_optional(EmbedAttachment.from_dict), metadata=no_export_meta) class-attribute

The video of the embed, only used by system embeds

add_field(name, value, inline=False)

Add a field to the embed.

Parameters:

Name Type Description Default
name str

The title of this field

required
value Any

The value in this field

required
inline bool

Should this field be inline with other fields?

False
Source code in interactions/models/discord/embed.py
415
416
417
418
419
420
421
422
423
424
425
426
427
428
def add_field(self, name: str, value: Any, inline: bool = False) -> "Embed":
    """
    Add a field to the embed.

    Args:
        name: The title of this field
        value: The value in this field
        inline: Should this field be inline with other fields?

    """
    self.fields.append(EmbedField(name, str(value), inline))
    self._fields_validation("fields", self.fields)

    return self

add_fields(*fields)

Add multiple fields to the embed.

Parameters:

Name Type Description Default
fields EmbedField | str | dict

The fields to add

()
Source code in interactions/models/discord/embed.py
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
def add_fields(self, *fields: EmbedField | str | dict) -> "Embed":
    """
    Add multiple fields to the embed.

    Args:
        fields: The fields to add

    """
    for _field in fields:
        if isinstance(_field, EmbedField):
            self.fields.append(_field)
            self._fields_validation("fields", self.fields)
        elif isinstance(_field, str):
            self.add_field(_field, _field)
        elif isinstance(_field, dict):
            self.add_field(**_field)
        else:
            raise TypeError(f"Expected EmbedField, str or dict, got {type(_field).__name__}")
    return self

add_image(image)

Add an image to the embed.

Note

To use multiple images, you must also set a url for this embed.

Warning

This takes advantage of an undocumented feature of the API, and may be removed at any time.

Parameters:

Name Type Description Default
image str

the image to add

required
Source code in interactions/models/discord/embed.py
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
def add_image(self, image: str) -> "Embed":
    """
    Add an image to the embed.

    Note:
        To use multiple images, you must also set a url for this embed.

    Warning:
        This takes advantage of an undocumented feature of the API, and may be removed at any time.

    Args:
        image: the image to add

    """
    if len(self.images) > 0 and not self.url:
        raise ValueError("To use multiple images, you must also set a url for this embed")
    self.images.append(EmbedAttachment(url=image))
    return self

set_author(name, url=None, icon_url=None)

Set the author field of the embed.

Parameters:

Name Type Description Default
name str

The text to go in the title section

required
url Optional[str]

A url link to the author

None
icon_url Optional[str]

A url of an image to use as the icon

None
Source code in interactions/models/discord/embed.py
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
def set_author(
    self,
    name: str,
    url: Optional[str] = None,
    icon_url: Optional[str] = None,
) -> "Embed":
    """
    Set the author field of the embed.

    Args:
        name: The text to go in the title section
        url: A url link to the author
        icon_url: A url of an image to use as the icon

    """
    self.author = EmbedAuthor(name=name, url=url, icon_url=icon_url)
    return self

Set the footer field of the embed.

Parameters:

Name Type Description Default
text str

The text to go in the title section

required
icon_url Optional[str]

A url of an image to use as the icon

None
Source code in interactions/models/discord/embed.py
403
404
405
406
407
408
409
410
411
412
413
def set_footer(self, text: str, icon_url: Optional[str] = None) -> "Embed":
    """
    Set the footer field of the embed.

    Args:
        text: The text to go in the title section
        icon_url: A url of an image to use as the icon

    """
    self.footer = EmbedFooter(text=text, icon_url=icon_url)
    return self

set_image(url)

Set the image of the embed.

Parameters:

Name Type Description Default
url str

the url of the image to use

required
Source code in interactions/models/discord/embed.py
353
354
355
356
357
358
359
360
361
362
def set_image(self, url: str) -> "Embed":
    """
    Set the image of the embed.

    Args:
        url: the url of the image to use

    """
    self.images = [EmbedAttachment(url=url)]
    return self

set_images(*images)

Set multiple images for the embed.

Note

To use multiple images, you must also set a url for this embed.

Warning

This takes advantage of an undocumented feature of the API, and may be removed at any time.

Parameters:

Name Type Description Default
images str

the images to use

()
Source code in interactions/models/discord/embed.py
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
def set_images(self, *images: str) -> "Embed":
    """
    Set multiple images for the embed.

    Note:
        To use multiple images, you must also set a url for this embed.

    Warning:
        This takes advantage of an undocumented feature of the API, and may be removed at any time.

    Args:
        images: the images to use

    """
    if len(self.images) + len(images) > 1 and not self.url:
        raise ValueError("To use multiple images, you must also set a url for this embed")

    self.images = [EmbedAttachment(url=url) for url in images]
    return self

set_thumbnail(url)

Set the thumbnail of the embed.

Parameters:

Name Type Description Default
url str

the url of the image to use

required
Source code in interactions/models/discord/embed.py
342
343
344
345
346
347
348
349
350
351
def set_thumbnail(self, url: str) -> "Embed":
    """
    Set the thumbnail of the embed.

    Args:
        url: the url of the image to use

    """
    self.thumbnail = EmbedAttachment(url=url)
    return self

EmbedAttachment

Bases: DictSerializationMixin

Representation of an attachment.

Attributes:

Name Type Description
url Optional[str]

Attachment url

proxy_url Optional[str]

Proxy url

height Optional[int]

Attachment height

width Optional[int]

Attachment width

Source code in interactions/models/discord/embed.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class EmbedAttachment(DictSerializationMixin):  # thumbnail or image or video
    """
    Representation of an attachment.

    Attributes:
        url: Attachment url
        proxy_url: Proxy url
        height: Attachment height
        width: Attachment width

    """

    url: Optional[str] = attrs.field(repr=False, default=None)
    proxy_url: Optional[str] = attrs.field(repr=False, default=None, metadata=no_export_meta)
    height: Optional[int] = attrs.field(repr=False, default=None, metadata=no_export_meta)
    width: Optional[int] = attrs.field(repr=False, default=None, metadata=no_export_meta)

    @classmethod
    def _process_dict(cls, data: Dict[str, Any]) -> Dict[str, Any]:
        return {"url": data} if isinstance(data, str) else data

    @property
    def size(self) -> tuple[Optional[int], Optional[int]]:
        return self.height, self.width

EmbedAuthor

Bases: DictSerializationMixin

Representation of an embed author.

Attributes:

Name Type Description
name Optional[str]

Name to show on embed

url Optional[str]

Url to go to when name is clicked

icon_url Optional[str]

Icon to show next to name

proxy_icon_url Optional[str]

Proxy icon url

Source code in interactions/models/discord/embed.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class EmbedAuthor(DictSerializationMixin):
    """
    Representation of an embed author.

    Attributes:
        name: Name to show on embed
        url: Url to go to when name is clicked
        icon_url: Icon to show next to name
        proxy_icon_url: Proxy icon url

    """

    name: Optional[str] = attrs.field(repr=False, default=None)
    url: Optional[str] = attrs.field(repr=False, default=None)
    icon_url: Optional[str] = attrs.field(repr=False, default=None)
    proxy_icon_url: Optional[str] = attrs.field(repr=False, default=None, metadata=no_export_meta)

    @name.validator
    def _name_validation(self, attribute: str, value: Any) -> None:
        if len(value) > EMBED_MAX_NAME_LENGTH:
            raise ValueError(f"Field name cannot exceed {EMBED_MAX_NAME_LENGTH} characters")

    def __len__(self) -> int:
        return len(self.name)

EmbedField

Bases: DictSerializationMixin

Representation of an embed field.

Attributes:

Name Type Description
name str

Field name

value str

Field value

inline bool

If the field should be inline

Source code in interactions/models/discord/embed.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class EmbedField(DictSerializationMixin):
    """
    Representation of an embed field.

    Attributes:
        name: Field name
        value: Field value
        inline: If the field should be inline

    """

    name: str = attrs.field(
        repr=False,
    )
    value: str = attrs.field(
        repr=False,
    )
    inline: bool = attrs.field(repr=False, default=False)

    @name.validator
    def _name_validation(self, attribute: str, value: Any) -> None:
        if len(value) > EMBED_MAX_NAME_LENGTH:
            raise ValueError(f"Field name cannot exceed {EMBED_MAX_NAME_LENGTH} characters")

    @value.validator
    def _value_validation(self, attribute: str, value: Any) -> None:
        if len(value) > EMBED_FIELD_VALUE_LENGTH:
            raise ValueError(f"Field value cannot exceed {EMBED_FIELD_VALUE_LENGTH} characters")

    def __len__(self) -> int:
        return len(self.name) + len(self.value)

EmbedFooter

Bases: DictSerializationMixin

Representation of an Embed Footer.

Attributes:

Name Type Description
text str

Footer text

icon_url Optional[str]

Footer icon url

proxy_icon_url Optional[str]

Proxy icon url

Source code in interactions/models/discord/embed.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class EmbedFooter(DictSerializationMixin):
    """
    Representation of an Embed Footer.

    Attributes:
        text: Footer text
        icon_url: Footer icon url
        proxy_icon_url: Proxy icon url

    """

    text: str = attrs.field(
        repr=False,
    )
    icon_url: Optional[str] = attrs.field(repr=False, default=None)
    proxy_icon_url: Optional[str] = attrs.field(repr=False, default=None, metadata=no_export_meta)

    @classmethod
    def converter(cls, ingest: Union[dict, str, "EmbedFooter"]) -> "EmbedFooter":
        """
        A converter to handle users passing raw strings or dictionaries as footers to the Embed object.

        Args:
            ingest: The data to convert

        Returns:
            An EmbedFooter object

        """
        return cls(text=ingest) if isinstance(ingest, str) else cls.from_dict(ingest)

    def __len__(self) -> int:
        return len(self.text)

converter(ingest) classmethod

A converter to handle users passing raw strings or dictionaries as footers to the Embed object.

Parameters:

Name Type Description Default
ingest Union[dict, str, EmbedFooter]

The data to convert

required

Returns:

Type Description
EmbedFooter

An EmbedFooter object

Source code in interactions/models/discord/embed.py
141
142
143
144
145
146
147
148
149
150
151
152
153
@classmethod
def converter(cls, ingest: Union[dict, str, "EmbedFooter"]) -> "EmbedFooter":
    """
    A converter to handle users passing raw strings or dictionaries as footers to the Embed object.

    Args:
        ingest: The data to convert

    Returns:
        An EmbedFooter object

    """
    return cls(text=ingest) if isinstance(ingest, str) else cls.from_dict(ingest)

EmbedProvider

Bases: DictSerializationMixin

Represents an embed's provider.

Note

Only used by system embeds, not bots

Attributes:

Name Type Description
name Optional[str]

Provider name

url Optional[str]

Provider url

Source code in interactions/models/discord/embed.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class EmbedProvider(DictSerializationMixin):
    """
    Represents an embed's provider.

    !!! note
        Only used by system embeds, not bots

    Attributes:
        name: Provider name
        url: Provider url

    """

    name: Optional[str] = attrs.field(repr=False, default=None)
    url: Optional[str] = attrs.field(repr=False, default=None)

process_embeds(embeds)

Process the passed embeds into a format discord will understand.

Parameters:

Name Type Description Default
embeds Optional[Union[List[Union[Embed, Dict]], Union[Embed, Dict]]]

List of dict / embeds to process

required

Returns:

Type Description
Optional[List[dict]]

formatted list for discord

Source code in interactions/models/discord/embed.py
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
def process_embeds(embeds: Optional[Union[List[Union[Embed, Dict]], Union[Embed, Dict]]]) -> Optional[List[dict]]:
    """
    Process the passed embeds into a format discord will understand.

    Args:
        embeds: List of dict / embeds to process

    Returns:
        formatted list for discord

    """
    if embeds is None:
        # Its just empty, so nothing to process.
        return embeds

    if isinstance(embeds, Embed):
        # Single embed, convert it to dict and wrap it into a list for discord.
        out = embeds.to_dict()
        return out if isinstance(out, list) else [out]
    if isinstance(embeds, dict):
        # We assume the dict correctly represents a single discord embed and just send it blindly
        # after wrapping it in a list for discord
        return [embeds]

    if isinstance(embeds, list):
        # A list of embeds, convert Embed to dict representation if needed.
        out = [embed.to_dict() if isinstance(embed, Embed) else embed for embed in embeds]
        if any(isinstance(embed, list) for embed in out):
            raise ValueError("You cannot send multiple embeds when using multiple images in a single embed")
        return out

    raise ValueError(f"Invalid embeds: {embeds}")

Asset

Represents a discord asset.

Attributes:

Name Type Description
BASE str

The cdn address for assets

url str

The URL of this asset

hash Optional[str]

The hash of this asset

Source code in interactions/models/discord/asset.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class Asset:
    """
    Represents a discord asset.

    Attributes:
        BASE str: The `cdn` address for assets
        url str: The URL of this asset
        hash Optional[str]: The hash of this asset

    """

    BASE = "https://cdn.discordapp.com"

    _client: "Client" = attrs.field(repr=False, metadata=no_export_meta)
    _url: str = attrs.field(repr=True)
    hash: Optional[str] = attrs.field(repr=True, default=None)

    @classmethod
    def from_path_hash(cls, client: "Client", path: str, asset_hash: str) -> "Asset":
        """
        Create an asset from a path and asset's hash.

        Args:
            client: The bot instance
            path: The CDN Endpoints for the type of asset.
            asset_hash: The hash representation of the target asset.

        Returns:
            A new Asset object

        """
        url = f"{cls.BASE}/{path.format(asset_hash)}"
        return cls(client=client, url=url, hash=asset_hash)

    @property
    def url(self) -> str:
        """The URL of this asset."""
        ext = ".gif" if self.animated else ".png"
        return f"{self._url}{ext}?size=4096"

    def as_url(self, *, extension: str | None = None, size: int = 4096) -> str:
        """
        Get the url of this asset.

        Args:
            extension: The extension to override the assets default with
            size: The size of asset to return

        Returns:
            A url for this asset with the given parameters

        """
        if not extension:
            extension = ".gif" if self.animated else ".png"
        elif not extension.startswith("."):
            extension = f".{extension}"

        return f"{self._url}{extension}?size={size}"

    @property
    def animated(self) -> bool:
        """True if this asset is animated."""
        # damn hashes with version numbers
        _hash = re.sub(r"^v\d+_", "", self.hash or "")
        return bool(self.hash) and _hash.startswith("a_")

    async def fetch(self, extension: Optional[str] = None, size: Optional[int] = None) -> bytes:
        """
        Fetch the asset from the Discord CDN.

        Args:
            extension: File extension based on the target image format
            size: The image size, can be any power of two between 16 and 4096.

        Returns:
            Raw byte array of the file

        Raises:
            ValueError: Incorrect file size if not power of 2 between 16 and 4096

        """
        if not extension:
            extension = ".gif" if self.animated else ".png"

        url = self._url + extension

        if size:
            if size == 0 or size & (size - 1) != 0:  # if not power of 2
                raise ValueError("Size should be a power of 2")
            if not 16 <= size <= 4096:
                raise ValueError("Size should be between 16 and 4096")

            url = f"{url}?size={size}"

        return await self._client.http.request_cdn(url, self)

    async def save(
        self,
        fd: Union[str, bytes, "PathLike", int],
        extension: Optional[str] = None,
        size: Optional[int] = None,
    ) -> int:
        """
        Save the asset to a local file.

        Args:
            fd: Destination path to save the file to.
            extension: File extension based on the target image format.
            size: The image size, can be any power of two between 16 and 4096.

        Returns:
            Status code result of file write

        """
        content = await self.fetch(extension=extension, size=size)
        with open(fd, "wb") as f:
            return f.write(content)

animated: bool property

True if this asset is animated.

url: str property

The URL of this asset.

as_url(*, extension=None, size=4096)

Get the url of this asset.

Parameters:

Name Type Description Default
extension str | None

The extension to override the assets default with

None
size int

The size of asset to return

4096

Returns:

Type Description
str

A url for this asset with the given parameters

Source code in interactions/models/discord/asset.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def as_url(self, *, extension: str | None = None, size: int = 4096) -> str:
    """
    Get the url of this asset.

    Args:
        extension: The extension to override the assets default with
        size: The size of asset to return

    Returns:
        A url for this asset with the given parameters

    """
    if not extension:
        extension = ".gif" if self.animated else ".png"
    elif not extension.startswith("."):
        extension = f".{extension}"

    return f"{self._url}{extension}?size={size}"

fetch(extension=None, size=None) async

Fetch the asset from the Discord CDN.

Parameters:

Name Type Description Default
extension Optional[str]

File extension based on the target image format

None
size Optional[int]

The image size, can be any power of two between 16 and 4096.

None

Returns:

Type Description
bytes

Raw byte array of the file

Raises:

Type Description
ValueError

Incorrect file size if not power of 2 between 16 and 4096

Source code in interactions/models/discord/asset.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
async def fetch(self, extension: Optional[str] = None, size: Optional[int] = None) -> bytes:
    """
    Fetch the asset from the Discord CDN.

    Args:
        extension: File extension based on the target image format
        size: The image size, can be any power of two between 16 and 4096.

    Returns:
        Raw byte array of the file

    Raises:
        ValueError: Incorrect file size if not power of 2 between 16 and 4096

    """
    if not extension:
        extension = ".gif" if self.animated else ".png"

    url = self._url + extension

    if size:
        if size == 0 or size & (size - 1) != 0:  # if not power of 2
            raise ValueError("Size should be a power of 2")
        if not 16 <= size <= 4096:
            raise ValueError("Size should be between 16 and 4096")

        url = f"{url}?size={size}"

    return await self._client.http.request_cdn(url, self)

from_path_hash(client, path, asset_hash) classmethod

Create an asset from a path and asset's hash.

Parameters:

Name Type Description Default
client Client

The bot instance

required
path str

The CDN Endpoints for the type of asset.

required
asset_hash str

The hash representation of the target asset.

required

Returns:

Type Description
Asset

A new Asset object

Source code in interactions/models/discord/asset.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@classmethod
def from_path_hash(cls, client: "Client", path: str, asset_hash: str) -> "Asset":
    """
    Create an asset from a path and asset's hash.

    Args:
        client: The bot instance
        path: The CDN Endpoints for the type of asset.
        asset_hash: The hash representation of the target asset.

    Returns:
        A new Asset object

    """
    url = f"{cls.BASE}/{path.format(asset_hash)}"
    return cls(client=client, url=url, hash=asset_hash)

save(fd, extension=None, size=None) async

Save the asset to a local file.

Parameters:

Name Type Description Default
fd Union[str, bytes, PathLike, int]

Destination path to save the file to.

required
extension Optional[str]

File extension based on the target image format.

None
size Optional[int]

The image size, can be any power of two between 16 and 4096.

None

Returns:

Type Description
int

Status code result of file write

Source code in interactions/models/discord/asset.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
async def save(
    self,
    fd: Union[str, bytes, "PathLike", int],
    extension: Optional[str] = None,
    size: Optional[int] = None,
) -> int:
    """
    Save the asset to a local file.

    Args:
        fd: Destination path to save the file to.
        extension: File extension based on the target image format.
        size: The image size, can be any power of two between 16 and 4096.

    Returns:
        Status code result of file write

    """
    content = await self.fetch(extension=extension, size=size)
    with open(fd, "wb") as f:
        return f.write(content)

BrandColors

Bases: Color, Enum

A collection of colors complying to the Discord Brand specification.

https://discord.com/branding

Source code in interactions/models/discord/color.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
class BrandColors(Color, Enum):
    """
    A collection of colors complying to the Discord Brand specification.

    https://discord.com/branding

    """

    BLURPLE = "#5865F2"
    GREEN = "#57F287"
    YELLOW = "#FEE75C"
    FUCHSIA = "#EB459E"
    RED = "#ED4245"
    WHITE = "#FFFFFF"
    BLACK = "#000000"

Color

Source code in interactions/models/discord/color.py
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
@attrs.define(eq=False, order=False, hash=False, init=False)
class Color:
    value: int = attrs.field(repr=True)
    """The color value as an integer."""

    def __init__(self, color: COLOR_TYPES | None = None) -> None:
        color = color or (0, 0, 0)
        if isinstance(color, int):
            self.value = color
        elif isinstance(color, (tuple, list)):
            color = tuple(color)
            self.rgb = color
        elif isinstance(color, str):
            if re.match(hex_regex, color):
                self.hex = color
            else:
                self.value = BrandColors[color].value
        else:
            raise TypeError

    def __str__(self) -> str:
        return self.hex

    # Helper methods

    @staticmethod
    def clamp(x, min_value=0, max_value=255) -> int:
        """Sanitise a value between a minimum and maximum value"""
        return max(min_value, min(x, max_value))

    # Constructor methods

    @classmethod
    def from_rgb(cls, r: int, g: int, b: int) -> "Color":
        """
        Create a Color object from red, green and blue values.

        Args:
            r: The red value.
            g: The green value.
            b: The blue value.

        Returns:
            A Color object.

        """
        return cls((r, g, b))

    @classmethod
    def from_hex(cls, value: str) -> "Color":
        """
        Create a Color object from a hexadecimal string.

        Args:
            value: The hexadecimal string.

        Returns:
            A Color object.

        """
        instance = cls()
        instance.hex = value
        return instance

    @classmethod
    def from_hsv(cls, h: int, s: int, v: int) -> "Color":
        """
        Create a Color object from a hue, saturation and value.

        Args:
            h: The hue value.
            s: The saturation value.
            v: The value value.

        Returns:
            A Color object.

        """
        instance = cls()
        instance.hsv = h, s, v
        return instance

    @classmethod
    def random(cls) -> "Color":
        """Returns random Color instance"""
        # FFFFFF == 16777215
        return cls(randint(0, 16777215))

    # Properties and setter methods

    def _get_byte(self, n) -> int:
        """
        Get the nth byte of the color value

        Args:
            n: The index of the byte to get.

        Returns:
            The nth byte of the color value.

        """
        return (self.value >> (8 * n)) & 255

    @property
    def r(self) -> int:
        """Red color value"""
        return self._get_byte(2)

    @property
    def g(self) -> int:
        """Green color value"""
        return self._get_byte(1)

    @property
    def b(self) -> int:
        """Blue color value"""
        return self._get_byte(0)

    @property
    def rgb(self) -> tuple[int, int, int]:
        """The red, green, blue color values in a tuple"""
        return self.r, self.g, self.b

    @rgb.setter
    def rgb(self, value: tuple[int, int, int]) -> None:
        """Set the color value from a tuple of (r, g, b) values"""
        # noinspection PyTypeChecker
        r, g, b = (self.clamp(v) for v in value)
        self.value = (r << 16) + (g << 8) + b

    @property
    def rgb_float(self) -> tuple[float, float, float]:
        """The red, green, blue color values in a tuple"""
        # noinspection PyTypeChecker
        return tuple(v / 255 for v in self.rgb)

    @property
    def hex(self) -> str:
        """Hexadecimal representation of color value"""
        r, g, b = self.rgb
        return f"#{r:02x}{g:02x}{b:02x}"

    @hex.setter
    def hex(self, value: str) -> None:
        """Set the color value from a hexadecimal string"""
        value = value.lstrip("#")
        # split hex into 3 parts of 2 digits and convert each to int from base-16 number
        self.rgb = tuple(int(value[i : i + 2], 16) for i in (0, 2, 4))

    @property
    def hsv(self) -> tuple[float, float, float]:
        """The hue, saturation, value color values in a tuple"""
        return colorsys.rgb_to_hsv(*self.rgb_float)

    @hsv.setter
    def hsv(self, value) -> None:
        """Set the color value from a tuple of (h, s, v) values"""
        self.rgb = tuple(round(v * 255) for v in colorsys.hsv_to_rgb(*value))

b: int property

Blue color value

g: int property

Green color value

hex: str writable property

Hexadecimal representation of color value

hsv: tuple[float, float, float] writable property

The hue, saturation, value color values in a tuple

r: int property

Red color value

rgb: tuple[int, int, int] writable property

The red, green, blue color values in a tuple

rgb_float: tuple[float, float, float] property

The red, green, blue color values in a tuple

value: int = attrs.field(repr=True) class-attribute

The color value as an integer.

clamp(x, min_value=0, max_value=255) staticmethod

Sanitise a value between a minimum and maximum value

Source code in interactions/models/discord/color.py
54
55
56
57
@staticmethod
def clamp(x, min_value=0, max_value=255) -> int:
    """Sanitise a value between a minimum and maximum value"""
    return max(min_value, min(x, max_value))

from_hex(value) classmethod

Create a Color object from a hexadecimal string.

Parameters:

Name Type Description Default
value str

The hexadecimal string.

required

Returns:

Type Description
Color

A Color object.

Source code in interactions/models/discord/color.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@classmethod
def from_hex(cls, value: str) -> "Color":
    """
    Create a Color object from a hexadecimal string.

    Args:
        value: The hexadecimal string.

    Returns:
        A Color object.

    """
    instance = cls()
    instance.hex = value
    return instance

from_hsv(h, s, v) classmethod

Create a Color object from a hue, saturation and value.

Parameters:

Name Type Description Default
h int

The hue value.

required
s int

The saturation value.

required
v int

The value value.

required

Returns:

Type Description
Color

A Color object.

Source code in interactions/models/discord/color.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
@classmethod
def from_hsv(cls, h: int, s: int, v: int) -> "Color":
    """
    Create a Color object from a hue, saturation and value.

    Args:
        h: The hue value.
        s: The saturation value.
        v: The value value.

    Returns:
        A Color object.

    """
    instance = cls()
    instance.hsv = h, s, v
    return instance

from_rgb(r, g, b) classmethod

Create a Color object from red, green and blue values.

Parameters:

Name Type Description Default
r int

The red value.

required
g int

The green value.

required
b int

The blue value.

required

Returns:

Type Description
Color

A Color object.

Source code in interactions/models/discord/color.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
@classmethod
def from_rgb(cls, r: int, g: int, b: int) -> "Color":
    """
    Create a Color object from red, green and blue values.

    Args:
        r: The red value.
        g: The green value.
        b: The blue value.

    Returns:
        A Color object.

    """
    return cls((r, g, b))

random() classmethod

Returns random Color instance

Source code in interactions/models/discord/color.py
111
112
113
114
115
@classmethod
def random(cls) -> "Color":
    """Returns random Color instance"""
    # FFFFFF == 16777215
    return cls(randint(0, 16777215))

FlatUIColors

Bases: Color, Enum

A collection of flat ui colours.

https://materialui.co/flatuicolors

Source code in interactions/models/discord/color.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
class FlatUIColors(Color, Enum):
    """
    A collection of flat ui colours.

    https://materialui.co/flatuicolors

    """

    TURQUOISE = "#1ABC9C"
    EMERLAND = "#2ECC71"
    PETERRIVER = "#3498DB"
    AMETHYST = "#9B59B6"
    WETASPHALT = "#34495E"
    GREENSEA = "#16A085"
    NEPHRITIS = "#27AE60"
    BELIZEHOLE = "#2980B9"
    WISTERIA = "#8E44AD"
    MIDNIGHTBLUE = "#2C3E50"
    SUNFLOWER = "#F1C40F"
    CARROT = "#E67E22"
    ALIZARIN = "#E74C3C"
    CLOUDS = "#ECF0F1"
    CONCRETE = "#95A5A6"
    ORANGE = "#F39C12"
    PUMPKIN = "#D35400"
    POMEGRANATE = "#C0392B"
    SILVER = "#BDC3C7"
    ASBESTOS = "#7F8C8D"

MaterialColors

Bases: Color, Enum

A collection of material ui colors.

https://www.materialpalette.com/

Source code in interactions/models/discord/color.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
class MaterialColors(Color, Enum):
    """
    A collection of material ui colors.

    https://www.materialpalette.com/

    """

    RED = "#F44336"
    PINK = "#E91E63"
    LAVENDER = "#EDB9F5"
    PURPLE = "#9C27B0"
    DEEP_PURPLE = "#673AB7"
    INDIGO = "#3F51B5"
    BLUE = "#2196F3"
    LIGHT_BLUE = "#03A9F4"
    CYAN = "#00BCD4"
    TEAL = "#009688"
    GREEN = "#4CAF50"
    LIGHT_GREEN = "#8BC34A"
    LIME = "#CDDC39"
    YELLOW = "#FFEB3B"
    AMBER = "#FFC107"
    ORANGE = "#FF9800"
    DEEP_ORANGE = "#FF5722"
    BROWN = "#795548"
    GREY = "#9E9E9E"
    BLUE_GREY = "#607D8B"

RoleColors

Bases: Color, Enum

A collection of the default role colors Discord provides.

Source code in interactions/models/discord/color.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
class RoleColors(Color, Enum):
    """A collection of the default role colors Discord provides."""

    TEAL = "#1ABC9C"
    DARK_TEAL = "#11806A"
    GREEN = "#2ECC71"
    DARK_GREEN = "#1F8B4C"
    BLUE = "#3498DB"
    DARK_BLUE = "#206694"
    PURPLE = "#9B59B6"
    DARK_PURPLE = "#71368A"
    MAGENTA = "#E91E63"
    DARK_MAGENTA = "#AD1457"
    YELLOW = "#F1C40F"
    DARK_YELLOW = "#C27C0E"
    ORANGE = "#E67E22"
    DARK_ORANGE = "#A84300"
    RED = "#E74C3C"
    DARK_RED = "#992D22"
    LIGHTER_GRAY = "#95A5A6"
    LIGHT_GRAY = "#979C9F"
    DARK_GRAY = "#607D8B"
    DARKER_GRAY = "#546E7A"

    # a certain other lib called the yellows this
    # i honestly cannot decide if they are or not
    # so why not satisfy everyone here?
    GOLD = YELLOW
    DARK_GOLD = DARK_YELLOW

    # aliases
    LIGHT_GREY = LIGHT_GRAY
    LIGHTER_GREY = LIGHTER_GRAY
    DARK_GREY = DARK_GRAY
    DARKER_GREY = DARKER_GRAY

process_color(color)

Process color to a format that can be used by discord.

Parameters:

Name Type Description Default
color Color | dict | COLOR_TYPES | None

The color to process.

required

Returns:

Type Description
int | None

The processed color value.

Source code in interactions/models/discord/color.py
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
def process_color(color: Color | dict | COLOR_TYPES | None) -> int | None:
    """
    Process color to a format that can be used by discord.

    Args:
        color: The color to process.

    Returns:
        The processed color value.

    """
    if not color:
        return None
    if isinstance(color, Color):
        return color.value
    if isinstance(color, dict):
        return color["value"]
    if isinstance(color, (tuple, list, str, int)):
        return Color(color).value

    raise ValueError(f"Invalid color: {type(color)}")

File

Representation of a file.

Used for sending files to discord.

Source code in interactions/models/discord/file.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class File:
    """
    Representation of a file.

    Used for sending files to discord.

    """

    file: Union["IOBase", BinaryIO, "Path", str] = attrs.field(repr=True)
    """Location of file to send or the bytes."""
    file_name: Optional[str] = attrs.field(repr=True, default=None)
    """Set a filename that will be displayed when uploaded to discord. If you leave this empty, the file will be called `file` by default"""
    description: Optional[str] = attrs.field(repr=True, default=None)
    """Optional description (ALT text) for the file."""
    content_type: Optional[str] = attrs.field(repr=True, default=None)
    """Override the content type of the file. If you leave this empty, the content type will be guessed from the file's data"""

    def __attrs_post_init__(self) -> None:
        if self.file_name is None:
            if isinstance(self.file, (IOBase, BinaryIO)):
                self.file_name = "file"
            else:
                self.file_name = Path(self.file).name

    def open_file(self) -> BinaryIO | IOBase:
        """
        Opens the file.

        Returns:
            A file-like BinaryIO object.

        """
        if isinstance(self.file, (IOBase, BinaryIO, bytes)):
            return self.file
        return open(str(self.file), "rb")

    def __enter__(self) -> "File":
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        if isinstance(self.file, (IOBase, BinaryIO)):
            self.file.close()

content_type: Optional[str] = attrs.field(repr=True, default=None) class-attribute

Override the content type of the file. If you leave this empty, the content type will be guessed from the file's data

description: Optional[str] = attrs.field(repr=True, default=None) class-attribute

Optional description (ALT text) for the file.

file: Union[IOBase, BinaryIO, Path, str] = attrs.field(repr=True) class-attribute

Location of file to send or the bytes.

file_name: Optional[str] = attrs.field(repr=True, default=None) class-attribute

Set a filename that will be displayed when uploaded to discord. If you leave this empty, the file will be called file by default

open_file()

Opens the file.

Returns:

Type Description
BinaryIO | IOBase

A file-like BinaryIO object.

Source code in interactions/models/discord/file.py
35
36
37
38
39
40
41
42
43
44
45
def open_file(self) -> BinaryIO | IOBase:
    """
    Opens the file.

    Returns:
        A file-like BinaryIO object.

    """
    if isinstance(self.file, (IOBase, BinaryIO, bytes)):
        return self.file
    return open(str(self.file), "rb")

open_file(file)

Opens the file.

Parameters:

Name Type Description Default
file UPLOADABLE_TYPE

The target file or path to file.

required

Returns:

Type Description
BinaryIO | IOBase

A file-like BinaryIO object.

Source code in interactions/models/discord/file.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def open_file(file: UPLOADABLE_TYPE) -> BinaryIO | IOBase:
    """
    Opens the file.

    Args:
        file: The target file or path to file.

    Returns:
        A file-like BinaryIO object.

    """
    match file:
        case File():
            return file.open_file()
        case IOBase() | BinaryIO():
            return file
        case Path() | str():
            return open(str(file), "rb")
        case _:
            raise ValueError(f"{file} is not a valid file")

Commands

BaseCommand

Bases: DictSerializationMixin, CallbackObject

An object all commands inherit from. Outlines the basic structure of a command, and handles checks.

Attributes:

Name Type Description
extension Optional[Extension]

The extension this command belongs to.

enabled bool

Whether this command is enabled

checks list

Any checks that must be run before this command can be run

callback Callable[..., Coroutine]

The coroutine to be called for this command

error_callback Callable[..., Coroutine]

The coroutine to be called when an error occurs

pre_run_callback Callable[..., Coroutine]

A coroutine to be called before this command is run but after the checks

post_run_callback Callable[..., Coroutine]

A coroutine to be called after this command has run

Source code in interactions/models/internal/command.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class BaseCommand(DictSerializationMixin, CallbackObject):
    """
    An object all commands inherit from. Outlines the basic structure of a command, and handles checks.

    Attributes:
        extension: The extension this command belongs to.
        enabled: Whether this command is enabled
        checks: Any checks that must be run before this command can be run
        callback: The coroutine to be called for this command
        error_callback: The coroutine to be called when an error occurs
        pre_run_callback: A coroutine to be called before this command is run **but** after the checks
        post_run_callback: A coroutine to be called after this command has run

    """

    extension: "Optional[Extension]" = attrs.field(
        repr=False,
        default=None,
        metadata=docs("The extension this command belongs to") | no_export_meta,
    )

    enabled: bool = attrs.field(
        repr=False, default=True, metadata=docs("Whether this can be run at all") | no_export_meta
    )
    checks: list = attrs.field(
        repr=False,
        factory=list,
        metadata=docs("Any checks that must be *checked* before the command can run") | no_export_meta,
    )
    cooldown: Cooldown = attrs.field(
        repr=False,
        default=MISSING,
        metadata=docs("An optional cooldown to apply to the command") | no_export_meta,
    )
    max_concurrency: MaxConcurrency = attrs.field(
        default=MISSING,
        metadata=docs("An optional maximum number of concurrent instances to apply to the command") | no_export_meta,
    )

    callback: Callable[..., Coroutine] = attrs.field(
        repr=False,
        default=None,
        metadata=docs("The coroutine to be called for this command") | no_export_meta,
    )
    error_callback: Callable[..., Coroutine] = attrs.field(
        repr=False,
        default=None,
        metadata=no_export_meta | docs("The coroutine to be called when an error occurs"),
    )
    pre_run_callback: Callable[..., Coroutine] = attrs.field(
        default=None,
        metadata=no_export_meta
        | docs("The coroutine to be called before the command is executed, **but** after the checks"),
    )
    post_run_callback: Callable[..., Coroutine] = attrs.field(
        repr=False,
        default=None,
        metadata=no_export_meta | docs("The coroutine to be called after the command has executed"),
    )

    def __attrs_post_init__(self) -> None:
        if self.callback is not None:
            if hasattr(self.callback, "checks"):
                self.checks += self.callback.checks
            if hasattr(self.callback, "cooldown"):
                self.cooldown = self.callback.cooldown
            if hasattr(self.callback, "max_concurrency"):
                self.max_concurrency = self.callback.max_concurrency

    def __hash__(self) -> int:
        return id(self)

    async def __call__(self, context: "BaseContext", *args, **kwargs) -> None:
        """
        Calls this command.

        Args:
            context: The context of this command
            args: Any
            kwargs: Any

        """
        # signals if a semaphore has been acquired, for exception handling
        # if present assume one will be acquired
        max_conc_acquired = self.max_concurrency is not MISSING

        try:
            if await self._can_run(context):
                if self.pre_run_callback is not None:
                    await self.call_with_binding(self.pre_run_callback, context, *args, **kwargs)

                if self.extension is not None and self.extension.extension_prerun:
                    for prerun in self.extension.extension_prerun:
                        await prerun(context, *args, **kwargs)

                await self.call_callback(self.callback, context)

                if self.post_run_callback is not None:
                    await self.call_with_binding(self.post_run_callback, context, *args, **kwargs)

                if self.extension is not None and self.extension.extension_postrun:
                    for postrun in self.extension.extension_postrun:
                        await postrun(context, *args, **kwargs)

        except Exception as e:
            # if a MaxConcurrencyReached-exception is raised a connection was never acquired
            max_conc_acquired = not isinstance(e, MaxConcurrencyReached)

            if self.error_callback:
                await self.error_callback(e, context, *args, **kwargs)
            elif self.extension and self.extension.extension_error:
                await self.extension.extension_error(e, context, *args, **kwargs)
            else:
                raise
        finally:
            if self.max_concurrency is not MISSING and max_conc_acquired:
                await self.max_concurrency.release(context)

    @staticmethod
    def _get_converter_function(anno: type[Converter] | Converter, name: str) -> Callable[[BaseContext, str], Any]:
        num_params = len(get_parameters(anno.convert))

        # if we have three parameters for the function, it's likely it has a self parameter
        # so we need to get rid of it by initing - typehinting hates this, btw!
        # the below line will error out if we aren't supposed to init it, so that works out
        try:
            actual_anno: Converter = anno() if num_params == 3 else anno  # type: ignore
        except TypeError:
            raise ValueError(
                f"{get_object_name(anno)} for {name} is invalid: converters must have exactly 2 arguments."
            ) from None

        # we can only get to this point while having three params if we successfully inited
        if num_params == 3:
            num_params -= 1

        if num_params != 2:
            raise ValueError(
                f"{get_object_name(anno)} for {name} is invalid: converters must have exactly 2 arguments."
            )

        return actual_anno.convert

    async def try_convert(self, converter: Optional[Callable], context: "BaseContext", value: Any) -> Any:
        if converter is None:
            return value
        return await maybe_coroutine(converter, context, value)

    def param_config(self, annotation: Any, name: str) -> Tuple[Callable, Optional[dict]]:
        # This thing is complicated. i.py-annotations can either be annotated directly, or they can be annotated with Annotated[str, CMD_*]
        # This helper function handles both cases, and returns a tuple of the converter and its config (if any)
        if annotation is None:
            return None
        if typing.get_origin(annotation) is Annotated and (args := typing.get_args(annotation)):
            for ann in args:
                v = getattr(ann, name, None)
                if v is not None:
                    return (ann, v)
        return (annotation, getattr(annotation, name, None))

    async def call_callback(self, callback: Callable, context: "BaseContext") -> None:
        await self.call_with_binding(callback, context, **context.kwargs)  # type: ignore

    async def _can_run(self, context: "BaseContext") -> bool:
        """
        Determines if this command can be run.

        Args:
            context: The context of the command

        """
        max_conc_acquired = False  # signals if a semaphore has been acquired, for exception handling

        try:
            if not self.enabled:
                return False

            for _c in self.checks:
                if not await _c(context):
                    raise CommandCheckFailure(self, _c, context)

            if self.extension and self.extension.extension_checks:
                for _c in self.extension.extension_checks:
                    if not await _c(context):
                        raise CommandCheckFailure(self, _c, context)

            if self.max_concurrency is not MISSING and not await self.max_concurrency.acquire(context):
                raise MaxConcurrencyReached(self, self.max_concurrency)

            if self.cooldown is not MISSING and not await self.cooldown.acquire_token(context):
                raise CommandOnCooldown(self, await self.cooldown.get_cooldown(context))

            return True

        except Exception:
            if max_conc_acquired:
                await self.max_concurrency.release(context)
            raise

    def add_check(self, check: Callable[..., Awaitable[bool]]) -> None:
        """Adds a check into the command."""
        self.checks.append(check)

    def error(self, call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
        """A decorator to declare a coroutine as one that will be run upon an error."""
        if not asyncio.iscoroutinefunction(call):
            raise TypeError("Error handler must be coroutine")
        self.error_callback = call
        return call

    def pre_run(self, call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
        """A decorator to declare a coroutine as one that will be run before the command."""
        if not asyncio.iscoroutinefunction(call):
            raise TypeError("pre_run must be coroutine")
        self.pre_run_callback = call
        return call

    def post_run(self, call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
        """A decorator to declare a coroutine as one that will be run after the command has."""
        if not asyncio.iscoroutinefunction(call):
            raise TypeError("post_run must be coroutine")
        self.post_run_callback = call
        return call

__call__(context, *args, **kwargs) async

Calls this command.

Parameters:

Name Type Description Default
context BaseContext

The context of this command

required
args

Any

()
kwargs

Any

{}
Source code in interactions/models/internal/command.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
async def __call__(self, context: "BaseContext", *args, **kwargs) -> None:
    """
    Calls this command.

    Args:
        context: The context of this command
        args: Any
        kwargs: Any

    """
    # signals if a semaphore has been acquired, for exception handling
    # if present assume one will be acquired
    max_conc_acquired = self.max_concurrency is not MISSING

    try:
        if await self._can_run(context):
            if self.pre_run_callback is not None:
                await self.call_with_binding(self.pre_run_callback, context, *args, **kwargs)

            if self.extension is not None and self.extension.extension_prerun:
                for prerun in self.extension.extension_prerun:
                    await prerun(context, *args, **kwargs)

            await self.call_callback(self.callback, context)

            if self.post_run_callback is not None:
                await self.call_with_binding(self.post_run_callback, context, *args, **kwargs)

            if self.extension is not None and self.extension.extension_postrun:
                for postrun in self.extension.extension_postrun:
                    await postrun(context, *args, **kwargs)

    except Exception as e:
        # if a MaxConcurrencyReached-exception is raised a connection was never acquired
        max_conc_acquired = not isinstance(e, MaxConcurrencyReached)

        if self.error_callback:
            await self.error_callback(e, context, *args, **kwargs)
        elif self.extension and self.extension.extension_error:
            await self.extension.extension_error(e, context, *args, **kwargs)
        else:
            raise
    finally:
        if self.max_concurrency is not MISSING and max_conc_acquired:
            await self.max_concurrency.release(context)

add_check(check)

Adds a check into the command.

Source code in interactions/models/internal/command.py
236
237
238
def add_check(self, check: Callable[..., Awaitable[bool]]) -> None:
    """Adds a check into the command."""
    self.checks.append(check)

error(call)

A decorator to declare a coroutine as one that will be run upon an error.

Source code in interactions/models/internal/command.py
240
241
242
243
244
245
def error(self, call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
    """A decorator to declare a coroutine as one that will be run upon an error."""
    if not asyncio.iscoroutinefunction(call):
        raise TypeError("Error handler must be coroutine")
    self.error_callback = call
    return call

post_run(call)

A decorator to declare a coroutine as one that will be run after the command has.

Source code in interactions/models/internal/command.py
254
255
256
257
258
259
def post_run(self, call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
    """A decorator to declare a coroutine as one that will be run after the command has."""
    if not asyncio.iscoroutinefunction(call):
        raise TypeError("post_run must be coroutine")
    self.post_run_callback = call
    return call

pre_run(call)

A decorator to declare a coroutine as one that will be run before the command.

Source code in interactions/models/internal/command.py
247
248
249
250
251
252
def pre_run(self, call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
    """A decorator to declare a coroutine as one that will be run before the command."""
    if not asyncio.iscoroutinefunction(call):
        raise TypeError("pre_run must be coroutine")
    self.pre_run_callback = call
    return call

check(check)

Add a check to a command.

Parameters:

Name Type Description Default
check Callable[..., Awaitable[bool]]

A coroutine as a check for this command

required
Source code in interactions/models/internal/command.py
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def check(check: Callable[..., Awaitable[bool]]) -> Callable[[CommandT], CommandT]:
    """
    Add a check to a command.

    Args:
        check: A coroutine as a check for this command

    """

    def wrapper(coro: CommandT) -> CommandT:
        if isinstance(coro, BaseCommand):
            coro.checks.append(check)
            return coro
        if not hasattr(coro, "checks"):
            coro.checks = []
        coro.checks.append(check)
        return coro

    return wrapper

cooldown(bucket, rate, interval, cooldown_system=None)

Add a cooldown to a command.

Parameters:

Name Type Description Default
bucket Buckets

The bucket used to track cooldowns

required
rate int

How many commands may be ran per interval

required
interval float

How many seconds to wait for a cooldown

required
cooldown_system typing.Type[CooldownSystem] | None

The cooldown system to use

None
Source code in interactions/models/internal/command.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
def cooldown(
    bucket: Buckets, rate: int, interval: float, cooldown_system: typing.Type[CooldownSystem] | None = None
) -> Callable[[CommandT], CommandT]:
    """
    Add a cooldown to a command.

    Args:
        bucket: The bucket used to track cooldowns
        rate: How many commands may be ran per interval
        interval: How many seconds to wait for a cooldown
        cooldown_system: The cooldown system to use

    """

    def wrapper(coro: CommandT) -> CommandT:
        cooldown_obj = Cooldown(bucket, rate, interval, cooldown_system=cooldown_system)

        coro.cooldown = cooldown_obj

        return coro

    return wrapper

max_concurrency(bucket, concurrent)

Add a maximum number of concurrent instances to the command.

Parameters:

Name Type Description Default
bucket Buckets

The bucket to enforce the maximum within

required
concurrent int

The maximum number of concurrent instances to allow

required
Source code in interactions/models/internal/command.py
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
def max_concurrency(bucket: Buckets, concurrent: int) -> Callable[[CommandT], CommandT]:
    """
    Add a maximum number of concurrent instances to the command.

    Args:
        bucket: The bucket to enforce the maximum within
        concurrent: The maximum number of concurrent instances to allow

    """

    def wrapper(coro: CommandT) -> CommandT:
        max_conc = MaxConcurrency(concurrent, bucket)

        coro.max_concurrency = max_conc

        return coro

    return wrapper

Application Commands

ActionRow

Bases: BaseComponent

Represents an action row.

Attributes:

Name Type Description
components list[Dict | BaseComponent]

A sequence of components contained within this action row

Source code in interactions/models/discord/components.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
class ActionRow(BaseComponent):
    """
    Represents an action row.

    Attributes:
        components list[Dict | BaseComponent]: A sequence of components contained within this action row

    """

    def __init__(self, *components: Dict | BaseComponent) -> None:
        if isinstance(components, (list, tuple)):
            # flatten user error
            components = list(components)

        self.components: list[Dict | BaseComponent] = [
            BaseComponent.from_dict_factory(c) if isinstance(c, dict) else c for c in components
        ]

        self.type: ComponentType = ComponentType.ACTION_ROW
        self._max_items = ACTION_ROW_MAX_ITEMS

    @classmethod
    def from_dict(cls, data: discord_typings.ActionRowData) -> "ActionRow":
        return cls(*data["components"])

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} type={self.type} components={len(self.components)}>"

    def add_component(self, *components: dict | BaseComponent) -> None:
        """
        Add a component to this action row.

        Args:
            *components: The component(s) to add.

        """
        if isinstance(components, (list, tuple)):
            # flatten user error
            components = list(components)

        self.components.extend(BaseComponent.from_dict_factory(c) if isinstance(c, dict) else c for c in components)

    @classmethod
    def split_components(cls, *components: dict | BaseComponent, count_per_row: int = 5) -> list["ActionRow"]:
        """
        Split components into action rows.

        Args:
            *components: The components to split.
            count_per_row: The amount of components to have per row.

        Returns:
            A list of action rows.

        """
        buffer = []
        action_rows = []

        for component in components:
            c_type = component.type if hasattr(component, "type") else component["type"]
            if c_type in (
                ComponentType.STRING_SELECT,
                ComponentType.USER_SELECT,
                ComponentType.ROLE_SELECT,
                ComponentType.MENTIONABLE_SELECT,
                ComponentType.CHANNEL_SELECT,
            ):
                # Selects can only be in their own row
                if buffer:
                    action_rows.append(cls(*buffer))
                    buffer = []
                action_rows.append(cls(component))
            else:
                buffer.append(component)
                if len(buffer) >= count_per_row:
                    action_rows.append(cls(*buffer))
                    buffer = []

        if buffer:
            action_rows.append(cls(*buffer))
        return action_rows

    def to_dict(self) -> discord_typings.ActionRowData:
        return {
            "type": self.type.value,  # type: ignore
            "components": [c.to_dict() for c in self.components],
        }

add_component(*components)

Add a component to this action row.

Parameters:

Name Type Description Default
*components dict | BaseComponent

The component(s) to add.

()
Source code in interactions/models/discord/components.py
145
146
147
148
149
150
151
152
153
154
155
156
157
def add_component(self, *components: dict | BaseComponent) -> None:
    """
    Add a component to this action row.

    Args:
        *components: The component(s) to add.

    """
    if isinstance(components, (list, tuple)):
        # flatten user error
        components = list(components)

    self.components.extend(BaseComponent.from_dict_factory(c) if isinstance(c, dict) else c for c in components)

split_components(*components, count_per_row=5) classmethod

Split components into action rows.

Parameters:

Name Type Description Default
*components dict | BaseComponent

The components to split.

()
count_per_row int

The amount of components to have per row.

5

Returns:

Type Description
list[ActionRow]

A list of action rows.

Source code in interactions/models/discord/components.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
@classmethod
def split_components(cls, *components: dict | BaseComponent, count_per_row: int = 5) -> list["ActionRow"]:
    """
    Split components into action rows.

    Args:
        *components: The components to split.
        count_per_row: The amount of components to have per row.

    Returns:
        A list of action rows.

    """
    buffer = []
    action_rows = []

    for component in components:
        c_type = component.type if hasattr(component, "type") else component["type"]
        if c_type in (
            ComponentType.STRING_SELECT,
            ComponentType.USER_SELECT,
            ComponentType.ROLE_SELECT,
            ComponentType.MENTIONABLE_SELECT,
            ComponentType.CHANNEL_SELECT,
        ):
            # Selects can only be in their own row
            if buffer:
                action_rows.append(cls(*buffer))
                buffer = []
            action_rows.append(cls(component))
        else:
            buffer.append(component)
            if len(buffer) >= count_per_row:
                action_rows.append(cls(*buffer))
                buffer = []

    if buffer:
        action_rows.append(cls(*buffer))
    return action_rows

BaseComponent

Bases: DictSerializationMixin

A base component class.

Warning

This should never be directly instantiated.

Source code in interactions/models/discord/components.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
class BaseComponent(DictSerializationMixin):
    """
    A base component class.

    !!! Warning
        This should never be directly instantiated.

    """

    type: ComponentType

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} type={self.type}>"

    @classmethod
    @abstractmethod
    def from_dict(cls, data: dict) -> "BaseComponent":
        """
        Create a component from a dictionary.

        Args:
            data: the dictionary to create the component from

        Returns:
            The created component.

        """
        raise NotImplementedError

    @classmethod
    def from_dict_factory(
        cls,
        data: dict,
        *,
        alternate_mapping: dict[ComponentType, "BaseComponent"] | None = None,
    ) -> "BaseComponent":
        """
        Creates a component from a payload.

        Args:
            data: the payload from Discord
            alternate_mapping: an optional mapping of component types to classes

        """
        data.pop("hash", None)  # redundant

        component_type = data.pop("type", None)

        mapping = alternate_mapping or TYPE_COMPONENT_MAPPING

        if component_class := mapping.get(component_type, None):
            return component_class.from_dict(data)
        raise TypeError(f"Unsupported component type for {data} ({component_type}), please consult the docs.")

from_dict(data) classmethod abstractmethod

Create a component from a dictionary.

Parameters:

Name Type Description Default
data dict

the dictionary to create the component from

required

Returns:

Type Description
BaseComponent

The created component.

Source code in interactions/models/discord/components.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@classmethod
@abstractmethod
def from_dict(cls, data: dict) -> "BaseComponent":
    """
    Create a component from a dictionary.

    Args:
        data: the dictionary to create the component from

    Returns:
        The created component.

    """
    raise NotImplementedError

from_dict_factory(data, *, alternate_mapping=None) classmethod

Creates a component from a payload.

Parameters:

Name Type Description Default
data dict

the payload from Discord

required
alternate_mapping dict[ComponentType, BaseComponent] | None

an optional mapping of component types to classes

None
Source code in interactions/models/discord/components.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@classmethod
def from_dict_factory(
    cls,
    data: dict,
    *,
    alternate_mapping: dict[ComponentType, "BaseComponent"] | None = None,
) -> "BaseComponent":
    """
    Creates a component from a payload.

    Args:
        data: the payload from Discord
        alternate_mapping: an optional mapping of component types to classes

    """
    data.pop("hash", None)  # redundant

    component_type = data.pop("type", None)

    mapping = alternate_mapping or TYPE_COMPONENT_MAPPING

    if component_class := mapping.get(component_type, None):
        return component_class.from_dict(data)
    raise TypeError(f"Unsupported component type for {data} ({component_type}), please consult the docs.")

BaseSelectMenu

Bases: InteractiveComponent

Represents a select menu component

Attributes:

Name Type Description
custom_id str

A developer-defined identifier for the button, max 100 characters.

placeholder str

The custom placeholder text to show if nothing is selected, max 100 characters.

min_values Optional[int]

The minimum number of items that must be chosen. (default 1, min 0, max 25)

max_values Optional[int]

The maximum number of items that can be chosen. (default 1, max 25)

disabled bool

Disable the select and make it not intractable, default false.

type Union[ComponentType, int]

The action role type number defined by discord. This cannot be modified.

Source code in interactions/models/discord/components.py
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
class BaseSelectMenu(InteractiveComponent):
    """
    Represents a select menu component

    Attributes:
        custom_id str: A developer-defined identifier for the button, max 100 characters.
        placeholder str: The custom placeholder text to show if nothing is selected, max 100 characters.
        min_values Optional[int]: The minimum number of items that must be chosen. (default 1, min 0, max 25)
        max_values Optional[int]: The maximum number of items that can be chosen. (default 1, max 25)
        disabled bool: Disable the select and make it not intractable, default false.
        type Union[ComponentType, int]: The action role type number defined by discord. This cannot be modified.

    """

    def __init__(
        self,
        *,
        placeholder: str | None = None,
        min_values: int = 1,
        max_values: int = 1,
        custom_id: str | None = None,
        disabled: bool = False,
    ) -> None:
        self.custom_id: str = custom_id or str(uuid.uuid4())
        self.placeholder: str | None = placeholder
        self.min_values: int = min_values
        self.max_values: int = max_values
        self.disabled: bool = disabled

        self.type: ComponentType = MISSING

    @classmethod
    def from_dict(cls, data: discord_typings.SelectMenuComponentData) -> "BaseSelectMenu":
        return cls(
            placeholder=data.get("placeholder"),
            min_values=data["min_values"],
            max_values=data["max_values"],
            custom_id=data["custom_id"],
            disabled=data.get("disabled", False),
        )

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} type={self.type} custom_id={self.custom_id} placeholder={self.placeholder} min_values={self.min_values} max_values={self.max_values} disabled={self.disabled}>"

    def to_dict(self) -> discord_typings.SelectMenuComponentData:
        return {
            "type": self.type.value,  # type: ignore
            "custom_id": self.custom_id,
            "placeholder": self.placeholder,
            "min_values": self.min_values,
            "max_values": self.max_values,
            "disabled": self.disabled,
        }

Button

Bases: InteractiveComponent

Represents a discord ui button.

Attributes:

Name Type Description
style optional[ButtonStyle, int]

Buttons come in a variety of styles to convey different types of actions.

label optional[str]

The text that appears on the button, max 80 characters.

emoji optional[Union[PartialEmoji, dict, str]]

The emoji that appears on the button.

custom_id Optional[str]

A developer-defined identifier for the button, max 100 characters.

sku_id Snowflake_Type | None

Optional[Snowflake_Type]: Identifier for a purchasable SKU, only available when using premium-style buttons

url Optional[str]

A url for link-style buttons.

disabled bool

Disable the button and make it not interactable, default false.

Source code in interactions/models/discord/components.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
class Button(InteractiveComponent):
    """
    Represents a discord ui button.

    Attributes:
        style optional[ButtonStyle, int]: Buttons come in a variety of styles to convey different types of actions.
        label optional[str]: The text that appears on the button, max 80 characters.
        emoji optional[Union[PartialEmoji, dict, str]]: The emoji that appears on the button.
        custom_id Optional[str]: A developer-defined identifier for the button, max 100 characters.
        sku_id: Optional[Snowflake_Type]: Identifier for a purchasable SKU, only available when using premium-style buttons
        url Optional[str]: A url for link-style buttons.
        disabled bool: Disable the button and make it not interactable, default false.

    """

    Styles: ButtonStyle = ButtonStyle

    def __init__(
        self,
        *,
        style: ButtonStyle | int,
        label: str | None = None,
        emoji: "PartialEmoji | None | str" = None,
        custom_id: str | None = None,
        sku_id: Snowflake_Type | None = None,
        url: str | None = None,
        disabled: bool = False,
    ) -> None:
        self.style: ButtonStyle = ButtonStyle(style)
        self.label: str | None = label
        self.emoji: "PartialEmoji | None" = emoji
        self.custom_id: str | None = custom_id
        self.sku_id: Snowflake_Type | None = sku_id
        self.url: str | None = url
        self.disabled: bool = disabled

        self.type: ComponentType = ComponentType.BUTTON

        if self.style == ButtonStyle.URL:
            if self.custom_id is not None:
                raise ValueError("URL buttons cannot have a custom_id.")
            if self.url is None:
                raise ValueError("URL buttons must have a url.")

        elif self.style == ButtonStyle.PREMIUM:
            if any(p is not None for p in (self.custom_id, self.url, self.emoji, self.label)):
                raise ValueError("Premium buttons cannot have a custom_id, url, emoji, or label.")
            if self.sku_id is None:
                raise ValueError("Premium buttons must have a sku_id.")

        elif self.custom_id is None:
            self.custom_id = str(uuid.uuid4())

        if self.style != ButtonStyle.PREMIUM and not self.label and not self.emoji:
            raise ValueError("Non-premium buttons must have a label or an emoji.")

        if isinstance(self.emoji, str):
            self.emoji = PartialEmoji.from_str(self.emoji)

    @classmethod
    def from_dict(cls, data: discord_typings.ButtonComponentData) -> "Button":
        emoji = process_emoji(data.get("emoji"))
        emoji = PartialEmoji.from_dict(emoji) if emoji else None
        return cls(
            style=ButtonStyle(data["style"]),
            label=data.get("label"),
            emoji=emoji,
            custom_id=data.get("custom_id"),
            sku_id=data.get("sku_id"),
            url=data.get("url"),
            disabled=data.get("disabled", False),
        )

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} type={self.type} style={self.style} label={self.label} emoji={self.emoji} custom_id={self.custom_id} sku_id={self.sku_id} url={self.url} disabled={self.disabled}>"

    def to_dict(self) -> discord_typings.ButtonComponentData:
        emoji = self.emoji.to_dict() if self.emoji else None
        if emoji and hasattr(emoji, "to_dict"):
            emoji = emoji.to_dict()

        return {
            "type": self.type.value,  # type: ignore
            "style": self.style.value,  # type: ignore
            "label": self.label,
            "emoji": emoji,
            "custom_id": self.custom_id,
            "sku_id": self.sku_id,
            "url": self.url,
            "disabled": self.disabled,
        }

InteractiveComponent

Bases: BaseComponent

A base interactive component class.

Warning

This should never be instantiated.

Source code in interactions/models/discord/components.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
class InteractiveComponent(BaseComponent):
    """
    A base interactive component class.

    !!! Warning
        This should never be instantiated.

    """

    type: ComponentType
    custom_id: str

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, dict):
            other = BaseComponent.from_dict_factory(other)
        return self.custom_id == other.custom_id and self.type == other.type

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} type={self.type} custom_id={self.custom_id}>"

RoleSelectMenu

Bases: DefaultableSelectMenu

Represents a user select component.

Attributes:

Name Type Description
custom_id str

A developer-defined identifier for the button, max 100 characters.

placeholder str

The custom placeholder text to show if nothing is selected, max 100 characters.

min_values Optional[int]

The minimum number of items that must be chosen. (default 1, min 0, max 25)

max_values Optional[int]

The maximum number of items that can be chosen. (default 1, max 25)

disabled bool

Disable the select and make it not intractable, default false.

type Union[ComponentType, int]

The action role type number defined by discord. This cannot be modified.

Source code in interactions/models/discord/components.py
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
class RoleSelectMenu(DefaultableSelectMenu):
    """
    Represents a user select component.

    Attributes:
        custom_id str: A developer-defined identifier for the button, max 100 characters.
        placeholder str: The custom placeholder text to show if nothing is selected, max 100 characters.
        min_values Optional[int]: The minimum number of items that must be chosen. (default 1, min 0, max 25)
        max_values Optional[int]: The maximum number of items that can be chosen. (default 1, max 25)
        disabled bool: Disable the select and make it not intractable, default false.
        type Union[ComponentType, int]: The action role type number defined by discord. This cannot be modified.

    """

    def __init__(
        self,
        *,
        placeholder: str | None = None,
        min_values: int = 1,
        max_values: int = 1,
        custom_id: str | None = None,
        disabled: bool = False,
        default_values: (
            list[
                Union[
                    "interactions.models.discord.BaseUser",
                    "interactions.models.discord.Role",
                    "interactions.models.discord.BaseChannel",
                    "interactions.models.discord.Member",
                    SelectDefaultValues,
                ],
            ]
            | None
        ) = None,
    ) -> None:
        super().__init__(
            placeholder=placeholder,
            min_values=min_values,
            max_values=max_values,
            custom_id=custom_id,
            disabled=disabled,
            defaults=default_values,
        )

        self.type: ComponentType = ComponentType.ROLE_SELECT

SelectDefaultValues

Bases: DiscordObject

Source code in interactions/models/discord/components.py
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
@attrs.define(eq=False, order=False, hash=False, slots=False)
class SelectDefaultValues(DiscordObject):
    id: Snowflake
    """ID of a user, role, or channel"""
    type: str
    """Type of value that id represents. Either "user", "role", or "channel"""

    @classmethod
    def from_object(cls, obj: DiscordObject) -> "SelectDefaultValues":
        """Create a default value from a discord object."""
        match obj:
            case d_models.User():
                return cls(client=obj._client, id=obj.id, type="user")
            case d_models.Member():
                return cls(client=obj._client, id=obj.id, type="user")
            case d_models.BaseChannel():
                return cls(client=obj._client, id=obj.id, type="channel")
            case d_models.Role():
                return cls(client=obj._client, id=obj.id, type="role")
            case _:
                raise TypeError(
                    f"Cannot convert {obj} of type {type(obj)} to a SelectDefaultValues - Expected User, Channel, Member, or Role"
                )

id: Snowflake class-attribute

ID of a user, role, or channel

type: str class-attribute

Type of value that id represents. Either "user", "role", or "channel

from_object(obj) classmethod

Create a default value from a discord object.

Source code in interactions/models/discord/components.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
@classmethod
def from_object(cls, obj: DiscordObject) -> "SelectDefaultValues":
    """Create a default value from a discord object."""
    match obj:
        case d_models.User():
            return cls(client=obj._client, id=obj.id, type="user")
        case d_models.Member():
            return cls(client=obj._client, id=obj.id, type="user")
        case d_models.BaseChannel():
            return cls(client=obj._client, id=obj.id, type="channel")
        case d_models.Role():
            return cls(client=obj._client, id=obj.id, type="role")
        case _:
            raise TypeError(
                f"Cannot convert {obj} of type {type(obj)} to a SelectDefaultValues - Expected User, Channel, Member, or Role"
            )

StringSelectMenu

Bases: BaseSelectMenu

Represents a string select component.

Attributes:

Name Type Description
options List[dict]

The choices in the select, max 25.

custom_id str

A developer-defined identifier for the button, max 100 characters.

placeholder str

The custom placeholder text to show if nothing is selected, max 100 characters.

min_values Optional[int]

The minimum number of items that must be chosen. (default 1, min 0, max 25)

max_values Optional[int]

The maximum number of items that can be chosen. (default 1, max 25)

disabled bool

Disable the select and make it not intractable, default false.

type Union[ComponentType, int]

The action role type number defined by discord. This cannot be modified.

Source code in interactions/models/discord/components.py
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
class StringSelectMenu(BaseSelectMenu):
    """
    Represents a string select component.

    Attributes:
        options List[dict]: The choices in the select, max 25.
        custom_id str: A developer-defined identifier for the button, max 100 characters.
        placeholder str: The custom placeholder text to show if nothing is selected, max 100 characters.
        min_values Optional[int]: The minimum number of items that must be chosen. (default 1, min 0, max 25)
        max_values Optional[int]: The maximum number of items that can be chosen. (default 1, max 25)
        disabled bool: Disable the select and make it not intractable, default false.
        type Union[ComponentType, int]: The action role type number defined by discord. This cannot be modified.

    """

    def __init__(
        self,
        *options: StringSelectOption
        | str
        | discord_typings.SelectMenuOptionData
        | list[StringSelectOption | str | discord_typings.SelectMenuOptionData],
        placeholder: str | None = None,
        min_values: int = 1,
        max_values: int = 1,
        custom_id: str | None = None,
        disabled: bool = False,
    ) -> None:
        super().__init__(
            placeholder=placeholder,
            min_values=min_values,
            max_values=max_values,
            custom_id=custom_id,
            disabled=disabled,
        )
        if isinstance(options, (list, tuple)) and len(options) == 1 and isinstance(options[0], (list, tuple)):
            # user passed in a list of options, expand it out
            options = options[0]

        self.options: list[StringSelectOption] = [StringSelectOption.converter(option) for option in options]
        self.type: ComponentType = ComponentType.STRING_SELECT

    @classmethod
    def from_dict(cls, data: discord_typings.SelectMenuComponentData) -> "StringSelectMenu":
        return cls(
            *data["options"],
            placeholder=data.get("placeholder"),
            min_values=data["min_values"],
            max_values=data["max_values"],
            custom_id=data["custom_id"],
            disabled=data.get("disabled", False),
        )

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} type={self.type} custom_id={self.custom_id} placeholder={self.placeholder} min_values={self.min_values} max_values={self.max_values} disabled={self.disabled} options={self.options}>"

    def to_dict(self) -> discord_typings.SelectMenuComponentData:
        return {
            **super().to_dict(),
            "options": [option.to_dict() for option in self.options],
        }

StringSelectOption

Bases: BaseComponent

Represents a select option.

Attributes:

Name Type Description
label str

The label (max 80 characters)

value str

The value of the select, this is whats sent to your bot

description Optional[str]

A description of this option

emoji Optional[Union[PartialEmoji, dict, str]

An emoji to show in this select option

default bool

Is this option selected by default

Source code in interactions/models/discord/components.py
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
class StringSelectOption(BaseComponent):
    """
    Represents a select option.

    Attributes:
        label str: The label (max 80 characters)
        value str: The value of the select, this is whats sent to your bot
        description Optional[str]: A description of this option
        emoji Optional[Union[PartialEmoji, dict, str]: An emoji to show in this select option
        default bool: Is this option selected by default

    """

    def __init__(
        self,
        *,
        label: str,
        value: str,
        description: str | None = None,
        emoji: "PartialEmoji | None | str" = None,
        default: bool = False,
    ) -> None:
        self.label: str = label
        self.value: str = value
        self.description: str | None = description
        self.emoji: PartialEmoji | None = emoji
        self.default: bool = default

        if isinstance(self.emoji, str):
            self.emoji = PartialEmoji.from_str(self.emoji)

    @classmethod
    def converter(cls, value: Any) -> "StringSelectOption":
        if isinstance(value, StringSelectOption):
            return value
        if isinstance(value, dict):
            return cls.from_dict(value)

        if isinstance(value, str):
            return cls(label=value, value=value)

        with contextlib.suppress(TypeError):
            possible_iter = iter(value)

            return cls(label=possible_iter[0], value=possible_iter[1])
        raise TypeError(f"Cannot convert {value} of type {type(value)} to a SelectOption")

    @classmethod
    def from_dict(cls, data: discord_typings.SelectMenuOptionData) -> "StringSelectOption":
        emoji = process_emoji(data.get("emoji"))
        emoji = PartialEmoji.from_dict(emoji) if emoji else None
        return cls(
            label=data["label"],
            value=data["value"],
            description=data.get("description"),
            emoji=emoji,
            default=data.get("default", False),
        )

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} label={self.label} value={self.value} description={self.description} emoji={self.emoji} default={self.default}>"

    def to_dict(self) -> discord_typings.SelectMenuOptionData:
        emoji = self.emoji.to_dict() if self.emoji else None
        if emoji and hasattr(emoji, "to_dict"):
            emoji = emoji.to_dict()

        return {
            "label": self.label,
            "value": self.value,
            "description": self.description,
            "emoji": emoji,
            "default": self.default,
        }

UserSelectMenu

Bases: DefaultableSelectMenu

Represents a user select component.

Attributes:

Name Type Description
custom_id str

A developer-defined identifier for the button, max 100 characters.

placeholder str

The custom placeholder text to show if nothing is selected, max 100 characters.

min_values Optional[int]

The minimum number of items that must be chosen. (default 1, min 0, max 25)

max_values Optional[int]

The maximum number of items that can be chosen. (default 1, max 25)

disabled bool

Disable the select and make it not intractable, default false.

type Union[ComponentType, int]

The action role type number defined by discord. This cannot be modified.

Source code in interactions/models/discord/components.py
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
class UserSelectMenu(DefaultableSelectMenu):
    """
    Represents a user select component.

    Attributes:
        custom_id str: A developer-defined identifier for the button, max 100 characters.
        placeholder str: The custom placeholder text to show if nothing is selected, max 100 characters.
        min_values Optional[int]: The minimum number of items that must be chosen. (default 1, min 0, max 25)
        max_values Optional[int]: The maximum number of items that can be chosen. (default 1, max 25)
        disabled bool: Disable the select and make it not intractable, default false.
        type Union[ComponentType, int]: The action role type number defined by discord. This cannot be modified.

    """

    def __init__(
        self,
        *,
        placeholder: str | None = None,
        min_values: int = 1,
        max_values: int = 1,
        custom_id: str | None = None,
        default_values: (
            list[
                Union[
                    "interactions.models.discord.BaseUser",
                    "interactions.models.discord.Role",
                    "interactions.models.discord.BaseChannel",
                    "interactions.models.discord.Member",
                    SelectDefaultValues,
                ],
            ]
            | None
        ) = None,
        disabled: bool = False,
    ) -> None:
        super().__init__(
            placeholder=placeholder,
            min_values=min_values,
            max_values=max_values,
            custom_id=custom_id,
            disabled=disabled,
            defaults=default_values,
        )

        self.type: ComponentType = ComponentType.USER_SELECT

get_components_ids(component)

Creates a generator with the custom_id of a component or list of components.

Parameters:

Name Type Description Default
component Union[str, dict, list, InteractiveComponent]

Objects to get custom_ids from

required

Returns:

Type Description
Iterator[str]

Generator with the custom_id of a component or list of components.

Raises:

Type Description
ValueError

Unknown component type

Source code in interactions/models/discord/components.py
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
def get_components_ids(component: Union[str, dict, list, InteractiveComponent]) -> Iterator[str]:
    """
    Creates a generator with the `custom_id` of a component or list of components.

    Args:
        component: Objects to get `custom_id`s from

    Returns:
        Generator with the `custom_id` of a component or list of components.

    Raises:
        ValueError: Unknown component type

    """
    if isinstance(component, str):
        yield component
    elif isinstance(component, dict):
        if component["type"] == ComponentType.actionrow:
            yield from (comp["custom_id"] for comp in component["components"] if "custom_id" in comp)
        elif "custom_id" in component:
            yield component["custom_id"]
    elif c_id := getattr(component, "custom_id", None):
        yield c_id
    elif isinstance(component, ActionRow):
        yield from (comp_id for comp in component.components for comp_id in get_components_ids(comp))

    elif isinstance(component, list):
        yield from (comp_id for comp in component for comp_id in get_components_ids(comp))
    else:
        raise ValueError(f"Unknown component type of {component} ({type(component)}). " f"Expected str, dict or list")

process_components(components)

Process the passed components into a format discord will understand.

Parameters:

Name Type Description Default
components Optional[Union[List[List[Union[BaseComponent, Dict]]], List[Union[BaseComponent, Dict]], BaseComponent, Dict]]

List of dict / components to process

required

Returns:

Type Description
List[Dict]

formatted dictionary for discord

Raises:

Type Description
ValueError

Invalid components

Source code in interactions/models/discord/components.py
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
def process_components(
    components: Optional[
        Union[
            List[List[Union[BaseComponent, Dict]]],
            List[Union[BaseComponent, Dict]],
            BaseComponent,
            Dict,
        ]
    ]
) -> List[Dict]:
    """
    Process the passed components into a format discord will understand.

    Args:
        components: List of dict / components to process

    Returns:
        formatted dictionary for discord

    Raises:
        ValueError: Invalid components

    """
    if not components:
        # Its just empty, so nothing to process.
        return components

    if isinstance(components, dict):
        # If a naked dictionary is passed, assume the user knows what they're doing and send it blindly
        # after wrapping it in a list for discord
        return [components]

    if issubclass(type(components), BaseComponent):
        # Naked component was passed
        components = [components]

    if isinstance(components, list):
        if all(isinstance(c, dict) for c in components):
            # user has passed a list of dicts, this is the correct format, blindly send it
            return components

        if all(isinstance(c, list) for c in components):
            # list of lists... actionRow-less sending
            return [ActionRow(*row).to_dict() for row in components]

        if all(issubclass(type(c), InteractiveComponent) for c in components):
            # list of naked components
            return [ActionRow(*components).to_dict()]

        if all(isinstance(c, ActionRow) for c in components):
            # we have a list of action rows
            return [action_row.to_dict() for action_row in components]

    raise ValueError(f"Invalid components: {components}")

spread_to_rows(*components, max_in_row=5)

A helper function that spreads your components into ActionRows of a set size.

Parameters:

Name Type Description Default
*components Union[ActionRow, Button, StringSelectMenu]

The components to spread, use None to explicit start a new row

()
max_in_row int

The maximum number of components in each row

5

Returns:

Type Description
List[ActionRow]

List[ActionRow] of components spread to rows

Raises:

Type Description
ValueError

Too many or few components or rows

Source code in interactions/models/discord/components.py
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
def spread_to_rows(*components: Union[ActionRow, Button, StringSelectMenu], max_in_row: int = 5) -> List[ActionRow]:
    """
    A helper function that spreads your components into `ActionRow`s of a set size.

    Args:
        *components: The components to spread, use `None` to explicit start a new row
        max_in_row: The maximum number of components in each row

    Returns:
        List[ActionRow] of components spread to rows

    Raises:
        ValueError: Too many or few components or rows

    """
    # todo: incorrect format errors
    if not components or len(components) > 25:
        raise ValueError("Number of components should be between 1 and 25.")
    return ActionRow.split_components(*components, count_per_row=max_in_row)

Modal

Source code in interactions/models/discord/modal.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
class Modal:
    def __init__(
        self,
        *components: InputText,
        title: str,
        custom_id: Optional[str] = None,
    ) -> None:
        self.title: str = title
        self.components: list[InputText] = list(components)
        self.custom_id: str = custom_id or str(uuid.uuid4())

        self.type = CallbackType.MODAL

    def to_dict(self) -> discord_typings.ModalInteractionData:
        return {
            "type": self.type,
            "data": {
                "title": self.title,
                "custom_id": self.custom_id,
                "components": [
                    {
                        "type": ComponentType.ACTION_ROW,
                        "components": [c.to_dict() if hasattr(c, "to_dict") else c],
                    }
                    for c in self.components
                ],
            },
        }

    def add_components(self, *components: InputText) -> None:
        """
        Add components to the modal.

        Args:
            *components: The components to add.

        """
        if len(components) == 1 and isinstance(components[0], (list, tuple)):
            components = components[0]
        self.components.extend(components)

add_components(*components)

Add components to the modal.

Parameters:

Name Type Description Default
*components InputText

The components to add.

()
Source code in interactions/models/discord/modal.py
159
160
161
162
163
164
165
166
167
168
169
def add_components(self, *components: InputText) -> None:
    """
    Add components to the modal.

    Args:
        *components: The components to add.

    """
    if len(components) == 1 and isinstance(components[0], (list, tuple)):
        components = components[0]
    self.components.extend(components)

CallbackType

Bases: IntEnum

Types of callback supported by interaction response.

Source code in interactions/models/internal/application_commands.py
195
196
197
198
199
200
201
202
203
204
class CallbackType(IntEnum):
    """Types of callback supported by interaction response."""

    PONG = 1
    CHANNEL_MESSAGE_WITH_SOURCE = 4
    DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE = 5
    DEFERRED_UPDATE_MESSAGE = 6
    UPDATE_MESSAGE = 7
    AUTOCOMPLETE_RESULT = 8
    MODAL = 9

ContextMenu

Bases: InteractionCommand

Represents a discord context menu.

Attributes:

Name Type Description
name LocalisedField

The name of this entry.

type CommandType

The type of entry (user or message).

Source code in interactions/models/internal/application_commands.py
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ContextMenu(InteractionCommand):
    """
    Represents a discord context menu.

    Attributes:
        name: The name of this entry.
        type: The type of entry (user or message).

    """

    name: LocalisedField = attrs.field(
        repr=False, metadata=docs("1-32 character name"), converter=LocalisedField.converter
    )
    type: CommandType = attrs.field(repr=False, metadata=docs("The type of command, defaults to 1 if not specified"))

    @type.validator
    def _type_validator(self, attribute: str, value: int) -> None:
        if not isinstance(value, CommandType):
            if value not in CommandType.__members__.values():
                raise ValueError("Context Menu type not recognised, please consult the docs.")
        elif value == CommandType.CHAT_INPUT:
            raise ValueError(
                "The CHAT_INPUT type is basically slash commands. Please use the @slash_command() " "decorator instead."
            )

    def to_dict(self) -> dict:
        data = super().to_dict()

        data["name"] = str(self.name)
        return data

InteractionCommand

Bases: BaseCommand

Represents a discord abstract interaction command.

Attributes:

Name Type Description
scope

Denotes whether its global or for specific guild.

default_member_permissions Optional[Permissions]

What permissions members need to have by default to use this command.

dm_permission bool

Should this command be available in DMs.

cmd_id Dict[str, Snowflake_Type]

The id of this command given by discord.

callback Callable[..., Coroutine]

The coroutine to callback when this interaction is received.

Source code in interactions/models/internal/application_commands.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class InteractionCommand(BaseCommand):
    """
    Represents a discord abstract interaction command.

    Attributes:
        scope: Denotes whether its global or for specific guild.
        default_member_permissions: What permissions members need to have by default to use this command.
        dm_permission: Should this command be available in DMs.
        cmd_id: The id of this command given by discord.
        callback: The coroutine to callback when this interaction is received.

    """

    name: LocalisedName | str = attrs.field(
        repr=False,
        metadata=docs("1-32 character name") | no_export_meta,
        converter=LocalisedName.converter,
    )
    scopes: List["Snowflake_Type"] = attrs.field(
        factory=lambda: [GLOBAL_SCOPE],
        converter=to_snowflake_list,
        metadata=docs("The scopes of this interaction. Global or guild ids") | no_export_meta,
    )
    default_member_permissions: Optional["Permissions"] = attrs.field(
        repr=False,
        default=None,
        metadata=docs("What permissions members need to have by default to use this command"),
    )
    dm_permission: bool = attrs.field(
        repr=False, default=True, metadata=docs("Whether this command is enabled in DMs (deprecated)") | no_export_meta
    )
    cmd_id: Dict[str, "Snowflake_Type"] = attrs.field(
        repr=False, factory=dict, metadata=docs("The unique IDs of this commands") | no_export_meta
    )  # scope: cmd_id
    callback: Callable[..., Coroutine] = attrs.field(
        repr=False,
        default=None,
        metadata=docs("The coroutine to call when this interaction is received") | no_export_meta,
    )
    auto_defer: "AutoDefer" = attrs.field(
        default=MISSING,
        metadata=docs("A system to automatically defer this command after a set duration") | no_export_meta,
    )
    nsfw: bool = attrs.field(repr=False, default=False, metadata=docs("This command should only work in NSFW channels"))
    integration_types: list[Union[IntegrationType, int]] = attrs.field(
        factory=lambda: [IntegrationType.GUILD_INSTALL],
        repr=False,
        metadata=docs("Installation context(s) where the command is available, only for globally-scoped commands."),
    )
    contexts: list[Union[ContextType, int]] = attrs.field(
        factory=lambda: [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL],
        repr=False,
        metadata=docs("Interaction context(s) where the command can be used, only for globally-scoped commands."),
    )
    _application_id: "Snowflake_Type" = attrs.field(repr=False, default=None, converter=optional(to_snowflake))

    def __attrs_post_init__(self) -> None:
        if self.callback is not None:
            if hasattr(self.callback, "auto_defer"):
                self.auto_defer = self.callback.auto_defer
            if hasattr(self.callback, "integration_types"):
                self.integration_types = self.callback.integration_types
            if hasattr(self.callback, "contexts"):
                self.contexts = self.callback.contexts

        super().__attrs_post_init__()

    @dm_permission.validator
    def _dm_permission_validator(self, attribute: str, value: bool) -> None:
        # since dm_permission is deprecated, ipy transforms it into something that isn't
        if not value:
            try:
                self.contexts.remove(ContextType.PRIVATE_CHANNEL)
            except ValueError:
                pass

            try:
                self.contexts.remove(ContextType.BOT_DM)
            except ValueError:
                pass

    def to_dict(self) -> dict:
        data = super().to_dict()

        data["name_localizations"] = self.name.to_locale_dict()

        if self.default_member_permissions is not None:
            data["default_member_permissions"] = str(int(self.default_member_permissions))
        else:
            data["default_member_permissions"] = None

        return data

    def mention(self, scope: Optional["Snowflake_Type"] = None) -> str:
        """
        Returns a string that would mention the interaction.

        Args:
            scope: If the command is available in multiple scope, specify which scope to get the mention for. Defaults to the first available one if not specified.

        Returns:
            The markdown mention.

        """
        if scope:
            cmd_id = self.get_cmd_id(scope=scope)
        else:
            cmd_id = next(iter(self.cmd_id.values()))

        return f"</{self.resolved_name}:{cmd_id}>"

    @property
    def resolved_name(self) -> str:
        """A representation of this interaction's name."""
        return str(self.name)

    def get_localised_name(self, locale: str) -> str:
        return self.name.get_locale(locale)

    def get_cmd_id(self, scope: "Snowflake_Type") -> "Snowflake_Type":
        return self.cmd_id.get(scope, self.cmd_id.get(GLOBAL_SCOPE, None))

    @property
    def is_subcommand(self) -> bool:
        return False

    async def _permission_enforcer(self, ctx: "BaseContext") -> bool:
        """A check that enforces Discord permissions."""
        # I wish this wasn't needed, but unfortunately Discord permissions cant be trusted to actually prevent usage
        return ctx.guild is not None if self.dm_permission is False else True

    def is_enabled(self, ctx: "BaseContext") -> bool:
        """
        Check if this command is enabled in the given context.

        Args:
            ctx: The context to check.

        Returns:
            Whether this command is enabled in the given context.

        """
        if not self.dm_permission and ctx.guild is None:
            return False
        if self.dm_permission and ctx.guild is None:
            # remaining checks are impossible if this is a DM and DMs are enabled
            return True

        if self.nsfw and not ctx.channel.is_nsfw():
            return False
        if cmd_perms := ctx.guild.command_permissions.get(self.get_cmd_id(ctx.guild.id)):
            if not cmd_perms.is_enabled_in_context(ctx):
                return False
        if self.default_member_permissions is not None:
            channel_perms = ctx.author.channel_permissions(ctx.channel)
            if any(perm not in channel_perms for perm in self.default_member_permissions):
                return False
        return True

resolved_name: str property

A representation of this interaction's name.

is_enabled(ctx)

Check if this command is enabled in the given context.

Parameters:

Name Type Description Default
ctx BaseContext

The context to check.

required

Returns:

Type Description
bool

Whether this command is enabled in the given context.

Source code in interactions/models/internal/application_commands.py
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
def is_enabled(self, ctx: "BaseContext") -> bool:
    """
    Check if this command is enabled in the given context.

    Args:
        ctx: The context to check.

    Returns:
        Whether this command is enabled in the given context.

    """
    if not self.dm_permission and ctx.guild is None:
        return False
    if self.dm_permission and ctx.guild is None:
        # remaining checks are impossible if this is a DM and DMs are enabled
        return True

    if self.nsfw and not ctx.channel.is_nsfw():
        return False
    if cmd_perms := ctx.guild.command_permissions.get(self.get_cmd_id(ctx.guild.id)):
        if not cmd_perms.is_enabled_in_context(ctx):
            return False
    if self.default_member_permissions is not None:
        channel_perms = ctx.author.channel_permissions(ctx.channel)
        if any(perm not in channel_perms for perm in self.default_member_permissions):
            return False
    return True

mention(scope=None)

Returns a string that would mention the interaction.

Parameters:

Name Type Description Default
scope Optional[Snowflake_Type]

If the command is available in multiple scope, specify which scope to get the mention for. Defaults to the first available one if not specified.

None

Returns:

Type Description
str

The markdown mention.

Source code in interactions/models/internal/application_commands.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
def mention(self, scope: Optional["Snowflake_Type"] = None) -> str:
    """
    Returns a string that would mention the interaction.

    Args:
        scope: If the command is available in multiple scope, specify which scope to get the mention for. Defaults to the first available one if not specified.

    Returns:
        The markdown mention.

    """
    if scope:
        cmd_id = self.get_cmd_id(scope=scope)
    else:
        cmd_id = next(iter(self.cmd_id.values()))

    return f"</{self.resolved_name}:{cmd_id}>"

LocalisedDesc

Bases: LocalisedField

A localisation object for descriptions.

Source code in interactions/models/internal/application_commands.py
120
121
122
123
124
125
126
127
128
129
130
@attrs.define(
    eq=False,
    order=False,
    hash=False,
    field_transformer=attrs_validator(desc_validator, skip_fields=["default_locale"]),
)
class LocalisedDesc(LocalisedField):
    """A localisation object for descriptions."""

    def __repr__(self) -> str:
        return super().__repr__()

LocalisedName

Bases: LocalisedField

A localisation object for names.

Source code in interactions/models/internal/application_commands.py
107
108
109
110
111
112
113
114
115
116
117
@attrs.define(
    eq=False,
    order=False,
    hash=False,
    field_transformer=attrs_validator(name_validator, skip_fields=["default_locale"]),
)
class LocalisedName(LocalisedField):
    """A localisation object for names."""

    def __repr__(self) -> str:
        return super().__repr__()

OptionType

Bases: IntEnum

Option types supported by slash commands.

Source code in interactions/models/internal/application_commands.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
class OptionType(IntEnum):
    """Option types supported by slash commands."""

    SUB_COMMAND = 1
    SUB_COMMAND_GROUP = 2
    STRING = 3
    INTEGER = 4
    BOOLEAN = 5
    USER = 6
    CHANNEL = 7
    ROLE = 8
    MENTIONABLE = 9
    NUMBER = 10
    ATTACHMENT = 11

    @classmethod
    def resolvable_types(cls) -> tuple["OptionType", ...]:
        """A tuple of all resolvable types."""
        return cls.USER, cls.CHANNEL, cls.ROLE, cls.MENTIONABLE, cls.ATTACHMENT

    @classmethod
    def static_types(cls) -> tuple["OptionType", ...]:
        """A tuple of all static types."""
        return cls.STRING, cls.INTEGER, cls.BOOLEAN, cls.NUMBER

    @classmethod
    def command_types(cls) -> tuple["OptionType", ...]:
        """A tuple of all command types."""
        return cls.SUB_COMMAND, cls.SUB_COMMAND_GROUP

    @classmethod
    def from_type(cls, t: type) -> "OptionType | None":
        """
        Convert data types to their corresponding OptionType.

        Args:
            t: The datatype to convert

        Returns:
            OptionType or None

        """
        if issubclass(t, str):
            return cls.STRING
        if issubclass(t, int):
            return cls.INTEGER
        if issubclass(t, bool):
            return cls.BOOLEAN
        if issubclass(t, BaseUser):
            return cls.USER
        if issubclass(t, channel.BaseChannel):
            return cls.CHANNEL
        if issubclass(t, Role):
            return cls.ROLE
        if issubclass(t, float):
            return cls.NUMBER

command_types() classmethod

A tuple of all command types.

Source code in interactions/models/internal/application_commands.py
162
163
164
165
@classmethod
def command_types(cls) -> tuple["OptionType", ...]:
    """A tuple of all command types."""
    return cls.SUB_COMMAND, cls.SUB_COMMAND_GROUP

from_type(t) classmethod

Convert data types to their corresponding OptionType.

Parameters:

Name Type Description Default
t type

The datatype to convert

required

Returns:

Type Description
OptionType | None

OptionType or None

Source code in interactions/models/internal/application_commands.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
@classmethod
def from_type(cls, t: type) -> "OptionType | None":
    """
    Convert data types to their corresponding OptionType.

    Args:
        t: The datatype to convert

    Returns:
        OptionType or None

    """
    if issubclass(t, str):
        return cls.STRING
    if issubclass(t, int):
        return cls.INTEGER
    if issubclass(t, bool):
        return cls.BOOLEAN
    if issubclass(t, BaseUser):
        return cls.USER
    if issubclass(t, channel.BaseChannel):
        return cls.CHANNEL
    if issubclass(t, Role):
        return cls.ROLE
    if issubclass(t, float):
        return cls.NUMBER

resolvable_types() classmethod

A tuple of all resolvable types.

Source code in interactions/models/internal/application_commands.py
152
153
154
155
@classmethod
def resolvable_types(cls) -> tuple["OptionType", ...]:
    """A tuple of all resolvable types."""
    return cls.USER, cls.CHANNEL, cls.ROLE, cls.MENTIONABLE, cls.ATTACHMENT

static_types() classmethod

A tuple of all static types.

Source code in interactions/models/internal/application_commands.py
157
158
159
160
@classmethod
def static_types(cls) -> tuple["OptionType", ...]:
    """A tuple of all static types."""
    return cls.STRING, cls.INTEGER, cls.BOOLEAN, cls.NUMBER

SlashCommand

Bases: InteractionCommand

Source code in interactions/models/internal/application_commands.py
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class SlashCommand(InteractionCommand):
    name: LocalisedName | str = attrs.field(repr=False, converter=LocalisedName.converter)
    description: LocalisedDesc | str = attrs.field(
        repr=False, default="No Description Set", converter=LocalisedDesc.converter
    )

    group_name: LocalisedName | str = attrs.field(
        repr=False, default=None, metadata=no_export_meta, converter=LocalisedName.converter
    )
    group_description: LocalisedDesc | str = attrs.field(
        repr=False,
        default="No Description Set",
        metadata=no_export_meta,
        converter=LocalisedDesc.converter,
    )

    sub_cmd_name: LocalisedName | str = attrs.field(
        repr=False, default=None, metadata=no_export_meta, converter=LocalisedName.converter
    )
    sub_cmd_description: LocalisedDesc | str = attrs.field(
        repr=False,
        default="No Description Set",
        metadata=no_export_meta,
        converter=LocalisedDesc.converter,
    )

    options: List[Union[SlashCommandOption, Dict]] = attrs.field(repr=False, factory=list)
    autocomplete_callbacks: dict = attrs.field(repr=False, factory=dict, metadata=no_export_meta)

    parameters: dict[str, SlashCommandParameter] = attrs.field(
        repr=False,
        factory=dict,
        metadata=no_export_meta,
    )
    _uses_arg: bool = attrs.field(repr=False, default=False, metadata=no_export_meta)

    @property
    def resolved_name(self) -> str:
        return (
            f"{self.name}"
            f"{f' {self.group_name}' if bool(self.group_name) else ''}"
            f"{f' {self.sub_cmd_name}' if bool(self.sub_cmd_name) else ''}"
        )

    def get_localised_name(self, locale: str) -> str:
        return (
            f"{self.name.get_locale(locale)}"
            f"{f' {self.group_name.get_locale(locale)}' if bool(self.group_name) else ''}"
            f"{f' {self.sub_cmd_name.get_locale(locale)}' if bool(self.sub_cmd_name) else ''}"
        )

    @property
    def is_subcommand(self) -> bool:
        return bool(self.sub_cmd_name)

    def __attrs_post_init__(self) -> None:
        if self.callback is not None and hasattr(self.callback, "options"):
            if not self.options:
                self.options = []
            self.options += self.callback.options

        super().__attrs_post_init__()

    def _add_option_from_anno_method(self, name: str, option: SlashCommandOption) -> None:
        if not self.options:
            self.options = []

        if option.name.default is None:
            option.name = LocalisedName.converter(name)
        else:
            option.argument_name = name

        self.options.append(option)

    def _parse_parameters(self) -> None:  # noqa: C901
        """
        Parses the parameters that this command has into a form i.py can use.

        This is purposely separated like this to allow "lazy parsing" - parsing
        as the command is added to a bot rather than being parsed immediately.
        This allows variables like "self" to be filtered out, and is useful for
        potential future additions.

        For slash commands, it is also much faster than inspecting the parameters
        each time the command is called.
        It also allows for us to deal with the "annotation method", where users
        put their options in the annotations itself.
        """
        if self.callback is None or self.parameters:
            return

        if self.has_binding:
            callback = functools.partial(self.callback, None, None)
        else:
            callback = functools.partial(self.callback, None)

        for param in get_parameters(callback).values():
            if param.kind == inspect._ParameterKind.VAR_POSITIONAL:
                self._uses_arg = True
                continue

            if param.kind == inspect._ParameterKind.VAR_KEYWORD:
                # in case it was set before
                # we prioritize **kwargs over *args
                self._uses_arg = False
                continue

            our_param = SlashCommandParameter(param.name, param.annotation, param.kind)
            our_param.default = param.default if param.default is not inspect._empty else MISSING

            if param.annotation is not inspect._empty:
                anno = param.annotation
                converter = None

                if _is_optional(anno):
                    anno = _remove_optional(anno)

                if isinstance(anno, SlashCommandOption):
                    # annotation method, get option and add it in
                    self._add_option_from_anno_method(param.name, anno)

                if isinstance(anno, Converter):
                    converter = anno
                elif typing.get_origin(anno) == Annotated:
                    if option := _get_option_from_annotated(anno):
                        # also annotation method
                        self._add_option_from_anno_method(param.name, option)

                    converter = _get_converter_from_annotated(anno)

                if converter:
                    our_param.converter = self._get_converter_function(converter, our_param.name)

            self.parameters[param.name] = our_param

        if self.options:
            for option in self.options:
                maybe_argument_name = (
                    option.argument_name if isinstance(option, SlashCommandOption) else option.get("argument_name")
                )
                if maybe_argument_name:
                    name = option.name if isinstance(option, SlashCommandOption) else option["name"]
                    try:
                        self.parameters[maybe_argument_name]._option_name = str(name)
                    except KeyError:
                        raise ValueError(
                            f'Argument name "{maybe_argument_name}" for "{name}" does not match any parameter in {self.resolved_name}\'s function.'
                        ) from None

    def to_dict(self) -> dict:
        data = super().to_dict()

        if self.is_subcommand:
            data["name"] = str(self.sub_cmd_name)
            data["description"] = str(self.sub_cmd_description)
            data["name_localizations"] = self.sub_cmd_name.to_locale_dict()
            data["description_localizations"] = self.sub_cmd_description.to_locale_dict()
            data.pop("default_member_permissions", None)
            data.pop("dm_permission", None)
            data.pop("nsfw", None)
        else:
            data["name_localizations"] = self.name.to_locale_dict()
            data["description_localizations"] = self.description.to_locale_dict()
        return data

    @options.validator
    def options_validator(self, attribute: str, value: List) -> None:
        if value:
            if not isinstance(value, list):
                raise TypeError("Options attribute must be either None or a list of options")
            if len(value) > SLASH_CMD_MAX_OPTIONS:
                raise ValueError(f"Slash commands can only hold {SLASH_CMD_MAX_OPTIONS} options")
            if value != sorted(
                value,
                key=lambda x: x.required if isinstance(x, SlashCommandOption) else x["required"],
                reverse=True,
            ):
                raise ValueError("Required options must go before optional options")

    def autocomplete(self, option_name: str) -> Callable[..., Coroutine]:
        """A decorator to declare a coroutine as an option autocomplete."""

        def wrapper(call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
            if not asyncio.iscoroutinefunction(call):
                raise TypeError("autocomplete must be coroutine")
            self.autocomplete_callbacks[option_name] = call

            if self.options:
                # automatically set the option's autocomplete attribute to True
                for opt in self.options:
                    if isinstance(opt, dict) and str(opt["name"]) == option_name:
                        opt["autocomplete"] = True
                    elif isinstance(opt, SlashCommandOption) and str(opt.name) == option_name:
                        opt.autocomplete = True

            return call

        option_name = option_name.lower()
        return wrapper

    def group(
        self, name: str | None = None, description: str = "No Description Set", inherit_checks: bool = True
    ) -> "SlashCommand":
        return SlashCommand(
            name=self.name,
            description=self.description,
            group_name=name,
            group_description=description,
            scopes=self.scopes,
            default_member_permissions=self.default_member_permissions,
            integration_types=self.integration_types,
            contexts=self.contexts,
            dm_permission=self.dm_permission,
            checks=self.checks.copy() if inherit_checks else [],
        )

    def subcommand(
        self,
        sub_cmd_name: Absent[LocalisedName | str] = MISSING,
        group_name: LocalisedName | str = None,
        sub_cmd_description: Absent[LocalisedDesc | str] = MISSING,
        group_description: Absent[LocalisedDesc | str] = MISSING,
        options: List[Union[SlashCommandOption, Dict]] | None = None,
        nsfw: bool = False,
        inherit_checks: bool = True,
    ) -> Callable[..., "SlashCommand"]:
        def wrapper(call: Callable[..., Coroutine]) -> "SlashCommand":
            nonlocal sub_cmd_name, sub_cmd_description

            if not asyncio.iscoroutinefunction(call):
                raise TypeError("Subcommand must be coroutine")

            if sub_cmd_description is MISSING:
                sub_cmd_description = call.__doc__ or "No Description Set"
            if sub_cmd_name is MISSING:
                sub_cmd_name = call.__name__

            return SlashCommand(
                name=self.name,
                description=self.description,
                group_name=group_name or self.group_name,
                group_description=group_description or self.group_description,
                sub_cmd_name=sub_cmd_name,
                sub_cmd_description=sub_cmd_description,
                default_member_permissions=self.default_member_permissions,
                integration_types=self.integration_types,
                contexts=self.contexts,
                dm_permission=self.dm_permission,
                options=options,
                callback=call,
                scopes=self.scopes,
                nsfw=nsfw,
                checks=self.checks.copy() if inherit_checks else [],
            )

        return wrapper

    async def call_callback(self, callback: typing.Callable, ctx: "InteractionContext") -> None:
        if not self.parameters:
            if self._uses_arg:
                return await self.call_with_binding(callback, ctx, *ctx.args)
            return await self.call_with_binding(callback, ctx)

        kwargs_copy = ctx.kwargs.copy()

        new_args = []
        new_kwargs = {}

        for param in self.parameters.values():
            value = kwargs_copy.pop(param.option_name, MISSING)
            if value is MISSING:
                continue

            if converter := param.converter:
                value = await maybe_coroutine(converter, ctx, value)

            if param.kind == inspect.Parameter.POSITIONAL_ONLY:
                new_args.append(value)
            else:
                new_kwargs[param.name] = value

        # i do want to address one thing: what happens if you have both *args and **kwargs
        # in your argument?
        # i would say passing in values for both makes sense... but they're very likely
        # going to overlap and cause issues and confusion
        # for the sake of simplicty, i.py assumes kwargs takes priority over args
        if kwargs_copy:
            if self._uses_arg:
                new_args.extend(kwargs_copy.values())
            else:
                new_kwargs |= kwargs_copy

        return await self.call_with_binding(callback, ctx, *new_args, **new_kwargs)

autocomplete(option_name)

A decorator to declare a coroutine as an option autocomplete.

Source code in interactions/models/internal/application_commands.py
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
def autocomplete(self, option_name: str) -> Callable[..., Coroutine]:
    """A decorator to declare a coroutine as an option autocomplete."""

    def wrapper(call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
        if not asyncio.iscoroutinefunction(call):
            raise TypeError("autocomplete must be coroutine")
        self.autocomplete_callbacks[option_name] = call

        if self.options:
            # automatically set the option's autocomplete attribute to True
            for opt in self.options:
                if isinstance(opt, dict) and str(opt["name"]) == option_name:
                    opt["autocomplete"] = True
                elif isinstance(opt, SlashCommandOption) and str(opt.name) == option_name:
                    opt.autocomplete = True

        return call

    option_name = option_name.lower()
    return wrapper

SlashCommandChoice

Bases: DictSerializationMixin

Represents a discord slash command choice.

Attributes:

Name Type Description
name LocalisedField | str

The name the user will see

value Union[str, int, float]

The data sent to your code when this choice is used

Source code in interactions/models/internal/application_commands.py
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class SlashCommandChoice(DictSerializationMixin):
    """
    Represents a discord slash command choice.

    Attributes:
        name: The name the user will see
        value: The data sent to your code when this choice is used

    """

    name: LocalisedField | str = attrs.field(repr=False, converter=LocalisedField.converter)
    value: Union[str, int, float] = attrs.field(
        repr=False,
    )

    def as_dict(self) -> dict:
        return {
            "name": str(self.name),
            "value": self.value,
            "name_localizations": self.name.to_locale_dict(),
        }

SlashCommandOption

Bases: DictSerializationMixin

Represents a discord slash command option.

Attributes:

Name Type Description
name LocalisedName | str

The name of this option

type Union[OptionType, int]

The type of option

description LocalisedDesc | str | str

The description of this option

required bool

"This option must be filled to use the command"

choices List[Union[SlashCommandChoice, Dict]]

A list of choices the user has to pick between

channel_types Optional[list[Union[ChannelType, int]]]

The channel types permitted. The option needs to be a channel

min_value Optional[float]

The minimum value permitted. The option needs to be an integer or float

max_value Optional[float]

The maximum value permitted. The option needs to be an integer or float

min_length Optional[int]

The minimum length of text a user can input. The option needs to be a string

max_length Optional[int]

The maximum length of text a user can input. The option needs to be a string

argument_name Optional[str]

The name of the argument to be used in the function. If not given, assumed to be the same as the name of the option

Source code in interactions/models/internal/application_commands.py
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class SlashCommandOption(DictSerializationMixin):
    """
    Represents a discord slash command option.

    Attributes:
        name: The name of this option
        type: The type of option
        description: The description of this option
        required: "This option must be filled to use the command"
        choices: A list of choices the user has to pick between
        channel_types: The channel types permitted. The option needs to be a channel
        min_value: The minimum value permitted. The option needs to be an integer or float
        max_value: The maximum value permitted. The option needs to be an integer or float
        min_length: The minimum length of text a user can input. The option needs to be a string
        max_length: The maximum length of text a user can input. The option needs to be a string
        argument_name: The name of the argument to be used in the function. If not given, assumed to be the same as the name of the option

    """

    name: LocalisedName | str = attrs.field(repr=False, converter=LocalisedName.converter)
    type: Union[OptionType, int] = attrs.field(
        repr=False,
    )
    description: LocalisedDesc | str | str = attrs.field(
        repr=False, default="No Description Set", converter=LocalisedDesc.converter
    )
    required: bool = attrs.field(repr=False, default=True)
    autocomplete: bool = attrs.field(repr=False, default=False)
    choices: List[Union[SlashCommandChoice, Dict]] = attrs.field(repr=False, factory=list)
    channel_types: Optional[list[Union[ChannelType, int]]] = attrs.field(repr=False, default=None)
    min_value: Optional[float] = attrs.field(repr=False, default=None)
    max_value: Optional[float] = attrs.field(repr=False, default=None)
    min_length: Optional[int] = attrs.field(repr=False, default=None)
    max_length: Optional[int] = attrs.field(repr=False, default=None)
    argument_name: Optional[str] = attrs.field(repr=False, default=None)

    @type.validator
    def _type_validator(self, attribute: str, value: int) -> None:
        if value in (OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP):
            raise ValueError(
                "Options cannot be SUB_COMMAND or SUB_COMMAND_GROUP. If you want to use subcommands, "
                "see the @sub_command() decorator."
            )

    @channel_types.validator
    def _channel_types_validator(self, attribute: str, value: Optional[list[OptionType]]) -> None:
        if value is not None:
            if self.type != OptionType.CHANNEL:
                raise ValueError("The option needs to be CHANNEL to use this")

            allowed_int = [channel_type.value for channel_type in ChannelType]
            for item in value:
                if (item not in allowed_int) and (item not in ChannelType):
                    raise ValueError(f"{value} is not allowed here")

    @min_value.validator
    def _min_value_validator(self, attribute: str, value: Optional[float]) -> None:
        if value is not None:
            if self.type not in [OptionType.INTEGER, OptionType.NUMBER]:
                raise ValueError("`min_value` can only be supplied with int or float options")

            if self.type == OptionType.INTEGER and isinstance(value, float):
                raise ValueError("`min_value` needs to be an int in an int option")

            if self.max_value is not None and self.min_value is not None and self.max_value < self.min_value:
                raise ValueError("`min_value` needs to be <= than `max_value`")

    @max_value.validator
    def _max_value_validator(self, attribute: str, value: Optional[float]) -> None:
        if value is not None:
            if self.type not in (OptionType.INTEGER, OptionType.NUMBER):
                raise ValueError("`max_value` can only be supplied with int or float options")

            if self.type == OptionType.INTEGER and isinstance(value, float):
                raise ValueError("`max_value` needs to be an int in an int option")

            if self.max_value and self.min_value and self.max_value < self.min_value:
                raise ValueError("`min_value` needs to be <= than `max_value`")

    @min_length.validator
    def _min_length_validator(self, attribute: str, value: Optional[int]) -> None:
        if value is not None:
            if self.type != OptionType.STRING:
                raise ValueError("`min_length` can only be supplied with string options")

            if self.max_length is not None and self.min_length is not None and self.max_length < self.min_length:
                raise ValueError("`min_length` needs to be <= than `max_length`")

            if self.min_length < 0:
                raise ValueError("`min_length` needs to be >= 0")

    @max_length.validator
    def _max_length_validator(self, attribute: str, value: Optional[int]) -> None:
        if value is not None:
            if self.type != OptionType.STRING:
                raise ValueError("`max_length` can only be supplied with string options")

            if self.min_length is not None and self.max_length is not None and self.max_length < self.min_length:
                raise ValueError("`min_length` needs to be <= than `max_length`")

            if self.max_length < 1:
                raise ValueError("`max_length` needs to be >= 1")

    def as_dict(self) -> dict:
        data = attrs.asdict(self)
        data.pop("argument_name", None)
        data["name"] = str(self.name)
        data["description"] = str(self.description)
        data["choices"] = [
            choice.as_dict() if isinstance(choice, SlashCommandChoice) else choice for choice in self.choices
        ]
        data["name_localizations"] = self.name.to_locale_dict()
        data["description_localizations"] = self.description.to_locale_dict()

        return data

application_commands_to_dict(commands, client)

Convert the command list into a format that would be accepted by discord.

Client.interactions should be the variable passed to this

Source code in interactions/models/internal/application_commands.py
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
def application_commands_to_dict(  # noqa: C901
    commands: Dict["Snowflake_Type", Dict[str, InteractionCommand]], client: "Client"
) -> dict:
    """
    Convert the command list into a format that would be accepted by discord.

    `Client.interactions` should be the variable passed to this

    """
    cmd_bases: defaultdict[str, list[InteractionCommand]] = defaultdict(list)  # {cmd_base: [commands]}
    """A store of commands organised by their base command"""
    output: defaultdict["Snowflake_Type", list[dict]] = defaultdict(list)
    """The output dictionary"""

    def squash_subcommand(subcommands: List) -> Dict:
        output_data = {}
        groups = {}
        sub_cmds = []
        for subcommand in subcommands:
            if not output_data:
                output_data = {
                    "name": str(subcommand.name),
                    "description": str(subcommand.description),
                    "options": [],
                    "default_member_permissions": (
                        str(int(subcommand.default_member_permissions))
                        if subcommand.default_member_permissions
                        else None
                    ),
                    "integration_types": subcommand.integration_types,
                    "contexts": subcommand.contexts,
                    "name_localizations": subcommand.name.to_locale_dict(),
                    "description_localizations": subcommand.description.to_locale_dict(),
                    "nsfw": subcommand.nsfw,
                }
            if bool(subcommand.group_name):
                if str(subcommand.group_name) not in groups:
                    groups[str(subcommand.group_name)] = {
                        "name": str(subcommand.group_name),
                        "description": str(subcommand.group_description),
                        "type": int(OptionType.SUB_COMMAND_GROUP),
                        "options": [],
                        "name_localizations": subcommand.group_name.to_locale_dict(),
                        "description_localizations": subcommand.group_description.to_locale_dict(),
                    }
                groups[str(subcommand.group_name)]["options"].append(
                    subcommand.to_dict() | {"type": int(OptionType.SUB_COMMAND)}
                )
            elif subcommand.is_subcommand:
                sub_cmds.append(subcommand.to_dict() | {"type": int(OptionType.SUB_COMMAND)})
        options = list(groups.values()) + sub_cmds
        output_data["options"] = options
        return output_data

    for _scope, cmds in commands.items():
        for cmd in cmds.values():
            cmd_name = str(cmd.name)
            if cmd not in cmd_bases[cmd_name]:
                cmd_bases[cmd_name].append(cmd)

    for cmd_list in cmd_bases.values():
        if any(c.is_subcommand for c in cmd_list):
            # validate all commands share required attributes
            scopes: list[Snowflake_Type] = list({s for c in cmd_list for s in c.scopes})
            base_description = next(
                (
                    c.description
                    for c in cmd_list
                    if str(c.description) is not None and str(c.description) != "No Description Set"
                ),
                "No Description Set",
            )
            nsfw = cmd_list[0].nsfw

            if any(str(c.description) not in (str(base_description), "No Description Set") for c in cmd_list):
                client.logger.warning(
                    f"Conflicting descriptions found in `{cmd_list[0].name}` subcommands; `{base_description!s}` will be used"
                )
            if any(c.default_member_permissions != cmd_list[0].default_member_permissions for c in cmd_list):
                raise ValueError(f"Conflicting `default_member_permissions` values found in `{cmd_list[0].name}`")
            if any(c.contexts != cmd_list[0].contexts for c in cmd_list):
                raise ValueError(f"Conflicting `contexts` values found in `{cmd_list[0].name}`")
            if any(c.integration_types != cmd_list[0].integration_types for c in cmd_list):
                raise ValueError(f"Conflicting `integration_types` values found in `{cmd_list[0].name}`")
            if any(c.dm_permission != cmd_list[0].dm_permission for c in cmd_list):
                raise ValueError(f"Conflicting `dm_permission` values found in `{cmd_list[0].name}`")
            if any(c.nsfw != nsfw for c in cmd_list):
                client.logger.warning(f"Conflicting `nsfw` values found in {cmd_list[0].name} - `True` will be used")
                nsfw = True

            for cmd in cmd_list:
                cmd.scopes = list(scopes)
                cmd.description = base_description
                cmd.nsfw = nsfw
            # end validation of attributes
            cmd_data = squash_subcommand(cmd_list)

            for s in scopes:
                output[s].append(cmd_data)
        else:
            for cmd in cmd_list:
                for s in cmd.scopes:
                    output[s].append(cmd.to_dict())
    return dict(output)

auto_defer(enabled=True, ephemeral=False, time_until_defer=0.0)

A decorator to add an auto defer to a application command.

Parameters:

Name Type Description Default
enabled bool

Should the command be deferred automatically

True
ephemeral bool

Should the command be deferred as ephemeral

False
time_until_defer float

How long to wait before deferring automatically

0.0
Source code in interactions/models/internal/application_commands.py
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
def auto_defer(
    enabled: bool = True, ephemeral: bool = False, time_until_defer: float = 0.0
) -> Callable[[InterCommandT], InterCommandT]:
    """
    A decorator to add an auto defer to a application command.

    Args:
        enabled: Should the command be deferred automatically
        ephemeral: Should the command be deferred as ephemeral
        time_until_defer: How long to wait before deferring automatically

    """

    def wrapper(func: InterCommandT) -> InterCommandT:
        if hasattr(func, "cmd_id"):
            raise ValueError("auto_defer decorators must be positioned under a slash_command decorator")
        func.auto_defer = AutoDefer(enabled=enabled, ephemeral=ephemeral, time_until_defer=time_until_defer)
        return func

    return wrapper

component_callback(*custom_id)

Register a coroutine as a component callback.

Component callbacks work the same way as commands, just using components as a way of invoking, instead of messages. Your callback will be given a single argument, ComponentContext

Note

This can optionally take a regex pattern, which will be used to match against the custom ID of the component.

If you do not supply a custom_id, the name of the coroutine will be used instead.

Parameters:

Name Type Description Default
*custom_id str | re.Pattern

The custom ID of the component to wait for

()
Source code in interactions/models/internal/application_commands.py
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
def component_callback(*custom_id: str | re.Pattern) -> Callable[[AsyncCallable], ComponentCommand]:
    """
    Register a coroutine as a component callback.

    Component callbacks work the same way as commands, just using components as a way of invoking, instead of messages.
    Your callback will be given a single argument, `ComponentContext`

    Note:
        This can optionally take a regex pattern, which will be used to match against the custom ID of the component.

        If you do not supply a `custom_id`, the name of the coroutine will be used instead.

    Args:
        *custom_id: The custom ID of the component to wait for

    """

    def wrapper(func: AsyncCallable) -> ComponentCommand:
        resolved_custom_id = custom_id or [func.__name__]

        if not asyncio.iscoroutinefunction(func):
            raise ValueError("Commands must be coroutines")

        return ComponentCommand(
            name=f"ComponentCallback::{resolved_custom_id}", callback=func, listeners=resolved_custom_id
        )

    custom_id = _unpack_helper(custom_id)
    custom_ids_validator(*custom_id)
    return wrapper

context_menu(name=MISSING, *, context_type, scopes=MISSING, default_member_permissions=None, integration_types=None, contexts=None, dm_permission=True)

A decorator to declare a coroutine as a Context Menu.

Parameters:

Name Type Description Default
name Absent[str | LocalisedName]

1-32 character name of the context menu, defaults to the name of the coroutine.

MISSING
context_type CommandType

The type of context menu

required
scopes Absent[List[Snowflake_Type]]

The scope this command exists within

MISSING
default_member_permissions Optional[Permissions]

What permissions members need to have by default to use this command.

None
integration_types Optional[List[Union[IntegrationType, int]]]

Installation context(s) where the command is available, only for globally-scoped commands.

None
contexts Optional[List[Union[ContextType, int]]]

Interaction context(s) where the command can be used, only for globally-scoped commands.

None
dm_permission bool

Should this command be available in DMs (deprecated).

True

Returns:

Type Description
Callable[[AsyncCallable], ContextMenu]

ContextMenu object

Source code in interactions/models/internal/application_commands.py
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
def context_menu(
    name: Absent[str | LocalisedName] = MISSING,
    *,
    context_type: "CommandType",
    scopes: Absent[List["Snowflake_Type"]] = MISSING,
    default_member_permissions: Optional["Permissions"] = None,
    integration_types: Optional[List[Union[IntegrationType, int]]] = None,
    contexts: Optional[List[Union[ContextType, int]]] = None,
    dm_permission: bool = True,
) -> Callable[[AsyncCallable], ContextMenu]:
    """
    A decorator to declare a coroutine as a Context Menu.

    Args:
        name: 1-32 character name of the context menu, defaults to the name of the coroutine.
        context_type: The type of context menu
        scopes: The scope this command exists within
        default_member_permissions: What permissions members need to have by default to use this command.
        integration_types: Installation context(s) where the command is available, only for globally-scoped commands.
        contexts: Interaction context(s) where the command can be used, only for globally-scoped commands.
        dm_permission: Should this command be available in DMs (deprecated).

    Returns:
        ContextMenu object

    """

    def wrapper(func: AsyncCallable) -> ContextMenu:
        if not asyncio.iscoroutinefunction(func):
            raise ValueError("Commands must be coroutines")

        perm = default_member_permissions
        if hasattr(func, "default_member_permissions"):
            if perm:
                perm = perm | func.default_member_permissions
            else:
                perm = func.default_member_permissions

        _name = name
        if _name is MISSING:
            _name = func.__name__

        cmd = ContextMenu(
            name=_name,
            type=context_type,
            scopes=scopes or [GLOBAL_SCOPE],
            default_member_permissions=perm,
            integration_types=integration_types or [IntegrationType.GUILD_INSTALL],
            contexts=contexts or [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL],
            dm_permission=dm_permission,
            callback=func,
        )
        return cmd

    return wrapper

contexts(guild=True, bot_dm=True, private_channel=True)

A decorator to set contexts where the command can be used for a application command.

Parameters:

Name Type Description Default
guild bool

Should the command be available in guilds

True
bot_dm bool

Should the command be available in bot DMs

True
private_channel bool

Should the command be available in private channels

True
Source code in interactions/models/internal/application_commands.py
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
def contexts(
    guild: bool = True, bot_dm: bool = True, private_channel: bool = True
) -> Callable[[InterCommandT], InterCommandT]:
    """
    A decorator to set contexts where the command can be used for a application command.

    Args:
        guild: Should the command be available in guilds
        bot_dm: Should the command be available in bot DMs
        private_channel: Should the command be available in private channels

    """
    kwargs = locals()

    def wrapper(func: InterCommandT) -> InterCommandT:
        if hasattr(func, "cmd_id"):
            raise ValueError("contexts decorators must be positioned under a command decorator")

        func.contexts = []
        for key in kwargs:
            if kwargs[key]:
                func.contexts.append(ContextType[key.upper()])

        return func

    return wrapper

global_autocomplete(option_name)

Decorator for global autocomplete functions

Parameters:

Name Type Description Default
option_name str

The name of the option to register the autocomplete function for

required

Returns:

Type Description
Callable[[AsyncCallable], GlobalAutoComplete]

The decorator

Source code in interactions/models/internal/application_commands.py
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
def global_autocomplete(option_name: str) -> Callable[[AsyncCallable], GlobalAutoComplete]:
    """
    Decorator for global autocomplete functions

    Args:
        option_name: The name of the option to register the autocomplete function for

    Returns:
        The decorator

    """

    def decorator(func: Callable) -> GlobalAutoComplete:
        if not asyncio.iscoroutinefunction(func):
            raise TypeError("Autocomplete functions must be coroutines")
        return GlobalAutoComplete(option_name, func)

    return decorator

integration_types(guild=True, user=False)

A decorator to set integration types for an application command.

Parameters:

Name Type Description Default
guild bool

Should the command be available for guilds

True
user bool

Should the command be available for individual users

False
Source code in interactions/models/internal/application_commands.py
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
def integration_types(guild: bool = True, user: bool = False) -> Callable[[InterCommandT], InterCommandT]:
    """
    A decorator to set integration types for an application command.

    Args:
        guild: Should the command be available for guilds
        user: Should the command be available for individual users

    """
    kwargs = locals()

    def wrapper(func: InterCommandT) -> InterCommandT:
        if hasattr(func, "cmd_id"):
            raise ValueError("integration_types decorators must be positioned under a command decorator")

        func.integration_types = []
        for key in kwargs:
            if kwargs[key]:
                func.integration_types.append(IntegrationType[key.upper() + "_INSTALL"])

        return func

    return wrapper

message_context_menu(name=MISSING, *, scopes=MISSING, default_member_permissions=None, integration_types=None, contexts=None, dm_permission=True)

A decorator to declare a coroutine as a Message Context Menu.

Parameters:

Name Type Description Default
name Absent[str | LocalisedName]

1-32 character name of the context menu, defaults to the name of the coroutine.

MISSING
scopes Absent[List[Snowflake_Type]]

The scope this command exists within

MISSING
default_member_permissions Optional[Permissions]

What permissions members need to have by default to use this command.

None
integration_types Optional[List[Union[IntegrationType, int]]]

Installation context(s) where the command is available, only for globally-scoped commands.

None
contexts Optional[List[Union[ContextType, int]]]

Interaction context(s) where the command can be used, only for globally-scoped commands.

None
dm_permission bool

Should this command be available in DMs (deprecated).

True

Returns:

Type Description
Callable[[AsyncCallable], ContextMenu]

ContextMenu object

Source code in interactions/models/internal/application_commands.py
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
def message_context_menu(
    name: Absent[str | LocalisedName] = MISSING,
    *,
    scopes: Absent[List["Snowflake_Type"]] = MISSING,
    default_member_permissions: Optional["Permissions"] = None,
    integration_types: Optional[List[Union[IntegrationType, int]]] = None,
    contexts: Optional[List[Union[ContextType, int]]] = None,
    dm_permission: bool = True,
) -> Callable[[AsyncCallable], ContextMenu]:
    """
    A decorator to declare a coroutine as a Message Context Menu.

    Args:
        name: 1-32 character name of the context menu, defaults to the name of the coroutine.
        scopes: The scope this command exists within
        default_member_permissions: What permissions members need to have by default to use this command.
        integration_types: Installation context(s) where the command is available, only for globally-scoped commands.
        contexts: Interaction context(s) where the command can be used, only for globally-scoped commands.
        dm_permission: Should this command be available in DMs (deprecated).

    Returns:
        ContextMenu object

    """
    return context_menu(
        name,
        context_type=CommandType.MESSAGE,
        scopes=scopes,
        default_member_permissions=default_member_permissions,
        integration_types=integration_types or [IntegrationType.GUILD_INSTALL],
        contexts=contexts or [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL],
        dm_permission=dm_permission,
    )

modal_callback(*custom_id)

Register a coroutine as a modal callback.

Modal callbacks work the same way as commands, just using modals as a way of invoking, instead of messages. Your callback will be given a single argument, ModalContext

Note

This can optionally take a regex pattern, which will be used to match against the custom ID of the modal.

If you do not supply a custom_id, the name of the coroutine will be used instead.

Parameters:

Name Type Description Default
*custom_id str | re.Pattern

The custom ID of the modal to wait for

()
Source code in interactions/models/internal/application_commands.py
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
def modal_callback(*custom_id: str | re.Pattern) -> Callable[[AsyncCallable], ModalCommand]:
    """
    Register a coroutine as a modal callback.

    Modal callbacks work the same way as commands, just using modals as a way of invoking, instead of messages.
    Your callback will be given a single argument, `ModalContext`

    Note:
        This can optionally take a regex pattern, which will be used to match against the custom ID of the modal.

        If you do not supply a `custom_id`, the name of the coroutine will be used instead.


    Args:
        *custom_id: The custom ID of the modal to wait for

    """

    def wrapper(func: AsyncCallable) -> ModalCommand:
        resolved_custom_id = custom_id or [func.__name__]

        if not asyncio.iscoroutinefunction(func):
            raise ValueError("Commands must be coroutines")

        return ModalCommand(name=f"ModalCallback::{resolved_custom_id}", callback=func, listeners=resolved_custom_id)

    custom_id = _unpack_helper(custom_id)
    custom_ids_validator(*custom_id)
    return wrapper

slash_command(name=MISSING, *, description=MISSING, scopes=MISSING, options=None, default_member_permissions=None, integration_types=None, contexts=None, dm_permission=True, sub_cmd_name=None, group_name=None, sub_cmd_description='No Description Set', group_description='No Description Set', nsfw=False)

A decorator to declare a coroutine as a slash command.

Note

While the base and group descriptions arent visible in the discord client, currently. We strongly advise defining them anyway, if you're using subcommands, as Discord has said they will be visible in one of the future ui updates.

Parameters:

Name Type Description Default
name Absent[str | LocalisedName]

1-32 character name of the command, defaults to the name of the coroutine.

MISSING
description Absent[str | LocalisedDesc]

1-100 character description of the command

MISSING
scopes Absent[List[Snowflake_Type]]

The scope this command exists within

MISSING
options Optional[List[Union[SlashCommandOption, Dict]]]

The parameters for the command, max 25

None
default_member_permissions Optional[Permissions]

What permissions members need to have by default to use this command.

None
integration_types Optional[List[Union[IntegrationType, int]]]

Installation context(s) where the command is available, only for globally-scoped commands.

None
contexts Optional[List[Union[ContextType, int]]]

Interaction context(s) where the command can be used, only for globally-scoped commands.

None
dm_permission bool

Should this command be available in DMs (deprecated).

True
sub_cmd_name str | LocalisedName

1-32 character name of the subcommand

None
sub_cmd_description str | LocalisedDesc

1-100 character description of the subcommand

'No Description Set'
group_name str | LocalisedName

1-32 character name of the group

None
group_description str | LocalisedDesc

1-100 character description of the group

'No Description Set'
nsfw bool

This command should only work in NSFW channels

False

Returns:

Type Description
Callable[[AsyncCallable], SlashCommand]

SlashCommand Object

Source code in interactions/models/internal/application_commands.py
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
def slash_command(
    name: Absent[str | LocalisedName] = MISSING,
    *,
    description: Absent[str | LocalisedDesc] = MISSING,
    scopes: Absent[List["Snowflake_Type"]] = MISSING,
    options: Optional[List[Union[SlashCommandOption, Dict]]] = None,
    default_member_permissions: Optional["Permissions"] = None,
    integration_types: Optional[List[Union[IntegrationType, int]]] = None,
    contexts: Optional[List[Union[ContextType, int]]] = None,
    dm_permission: bool = True,
    sub_cmd_name: str | LocalisedName = None,
    group_name: str | LocalisedName = None,
    sub_cmd_description: str | LocalisedDesc = "No Description Set",
    group_description: str | LocalisedDesc = "No Description Set",
    nsfw: bool = False,
) -> Callable[[AsyncCallable], SlashCommand]:
    """
    A decorator to declare a coroutine as a slash command.

    !!! note
        While the base and group descriptions arent visible in the discord client, currently.
        We strongly advise defining them anyway, if you're using subcommands, as Discord has said they will be visible in
        one of the future ui updates.

    Args:
        name: 1-32 character name of the command, defaults to the name of the coroutine.
        description: 1-100 character description of the command
        scopes: The scope this command exists within
        options: The parameters for the command, max 25
        default_member_permissions: What permissions members need to have by default to use this command.
        integration_types: Installation context(s) where the command is available, only for globally-scoped commands.
        contexts: Interaction context(s) where the command can be used, only for globally-scoped commands.
        dm_permission: Should this command be available in DMs (deprecated).
        sub_cmd_name: 1-32 character name of the subcommand
        sub_cmd_description: 1-100 character description of the subcommand
        group_name: 1-32 character name of the group
        group_description: 1-100 character description of the group
        nsfw: This command should only work in NSFW channels

    Returns:
        SlashCommand Object

    """

    def wrapper(func: AsyncCallable) -> SlashCommand:
        if not asyncio.iscoroutinefunction(func):
            raise ValueError("Commands must be coroutines")

        perm = default_member_permissions
        if hasattr(func, "default_member_permissions"):
            if perm:
                perm = perm | func.default_member_permissions
            else:
                perm = func.default_member_permissions

        _name = name
        if _name is MISSING:
            _name = func.__name__

        _description = description
        if _description is MISSING:
            _description = func.__doc__ or "No Description Set"

        cmd = SlashCommand(
            name=_name,
            group_name=group_name,
            group_description=group_description,
            sub_cmd_name=sub_cmd_name,
            sub_cmd_description=sub_cmd_description,
            description=_description,
            scopes=scopes or [GLOBAL_SCOPE],
            default_member_permissions=perm,
            integration_types=integration_types or [IntegrationType.GUILD_INSTALL],
            contexts=contexts or [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL],
            dm_permission=dm_permission,
            callback=func,
            options=options,
            nsfw=nsfw,
        )

        return cmd

    return wrapper

slash_default_member_permission(permission)

A decorator to permissions members need to have by default to use a command.

Parameters:

Name Type Description Default
permission Permissions

The permissions to require for to this command

required
Source code in interactions/models/internal/application_commands.py
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
def slash_default_member_permission(
    permission: "Permissions",
) -> Callable[[SlashCommandT], SlashCommandT]:
    """
    A decorator to permissions members need to have by default to use a command.

    Args:
        permission: The permissions to require for to this command

    """

    def wrapper(func: SlashCommandT) -> SlashCommandT:
        if hasattr(func, "cmd_id"):
            raise ValueError(
                "slash_default_member_permission decorators must be positioned under a slash_command decorator"
            )

        if not hasattr(func, "default_member_permissions") or func.default_member_permissions is None:
            func.default_member_permissions = permission
        else:
            func.default_member_permissions = func.default_member_permissions | permission
        return func

    return wrapper

slash_option(name, description, opt_type, required=False, autocomplete=False, choices=None, channel_types=None, min_value=None, max_value=None, min_length=None, max_length=None, argument_name=None)

A decorator to add an option to a slash command.

Parameters:

Name Type Description Default
name str

1-32 lowercase character name matching ^[\w-]{1,32}$

required
opt_type Union[OptionType, int]

The type of option

required
description str

1-100 character description of option

required
required bool

If the parameter is required or optional--default false

False
autocomplete bool

If autocomplete interactions are enabled for this STRING, INTEGER, or NUMBER type option

False
choices List[Union[SlashCommandChoice, dict]] | None

A list of choices the user has to pick between (max 25)

None
channel_types Optional[list[Union[ChannelType, int]]]

The channel types permitted. The option needs to be a channel

None
min_value Optional[float]

The minimum value permitted. The option needs to be an integer or float

None
max_value Optional[float]

The maximum value permitted. The option needs to be an integer or float

None
min_length Optional[int]

The minimum length of text a user can input. The option needs to be a string

None
max_length Optional[int]

The maximum length of text a user can input. The option needs to be a string

None
argument_name Optional[str]

The name of the argument to be used in the function. If not given, assumed to be the same as the name of the option

None
Source code in interactions/models/internal/application_commands.py
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
def slash_option(
    name: str,
    description: str,
    opt_type: Union[OptionType, int],
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union[SlashCommandChoice, dict]] | None = None,
    channel_types: Optional[list[Union[ChannelType, int]]] = None,
    min_value: Optional[float] = None,
    max_value: Optional[float] = None,
    min_length: Optional[int] = None,
    max_length: Optional[int] = None,
    argument_name: Optional[str] = None,
) -> Callable[[SlashCommandT], SlashCommandT]:
    r"""
    A decorator to add an option to a slash command.

    Args:
        name: 1-32 lowercase character name matching ^[\w-]{1,32}$
        opt_type: The type of option
        description: 1-100 character description of option
        required: If the parameter is required or optional--default false
        autocomplete: If autocomplete interactions are enabled for this STRING, INTEGER, or NUMBER type option
        choices: A list of choices the user has to pick between (max 25)
        channel_types: The channel types permitted. The option needs to be a channel
        min_value: The minimum value permitted. The option needs to be an integer or float
        max_value: The maximum value permitted. The option needs to be an integer or float
        min_length: The minimum length of text a user can input. The option needs to be a string
        max_length: The maximum length of text a user can input. The option needs to be a string
        argument_name: The name of the argument to be used in the function. If not given, assumed to be the same as the name of the option

    """

    def wrapper(func: SlashCommandT) -> SlashCommandT:
        if hasattr(func, "cmd_id"):
            raise ValueError("slash_option decorators must be positioned under a slash_command decorator")

        option = SlashCommandOption(
            name=name,
            type=opt_type,
            description=description,
            required=required,
            autocomplete=autocomplete,
            choices=choices or [],
            channel_types=channel_types,
            min_value=min_value,
            max_value=max_value,
            min_length=min_length,
            max_length=max_length,
            argument_name=argument_name,
        )
        if not hasattr(func, "options"):
            func.options = []
        func.options.insert(0, option)
        return func

    return wrapper

subcommand(base, *, subcommand_group=None, name=MISSING, description=MISSING, base_description=None, base_desc=None, base_default_member_permissions=None, base_integration_types=None, base_contexts=None, base_dm_permission=True, subcommand_group_description=None, sub_group_desc=None, scopes=None, options=None, nsfw=False)

A decorator specifically tailored for creating subcommands.

Parameters:

Name Type Description Default
base str | LocalisedName

The name of the base command

required
subcommand_group Optional[str | LocalisedName]

The name of the subcommand group, if any.

None
name Absent[str | LocalisedName]

The name of the subcommand, defaults to the name of the coroutine.

MISSING
description Absent[str | LocalisedDesc]

The description of the subcommand

MISSING
base_description Optional[str | LocalisedDesc]

The description of the base command

None
base_desc Optional[str | LocalisedDesc]

An alias of base_description

None
base_default_member_permissions Optional[Permissions]

What permissions members need to have by default to use this command.

None
base_integration_types Optional[List[Union[IntegrationType, int]]]

Installation context(s) where the command is available, only for globally-scoped commands.

None
base_contexts Optional[List[Union[ContextType, int]]]

Interaction context(s) where the command can be used, only for globally-scoped commands.

None
base_dm_permission bool

Should this command be available in DMs (deprecated).

True
subcommand_group_description Optional[str | LocalisedDesc]

Description of the subcommand group

None
sub_group_desc Optional[str | LocalisedDesc]

An alias for subcommand_group_description

None
scopes List[Snowflake_Type] | None

The scopes of which this command is available, defaults to GLOBAL_SCOPE

None
options List[dict] | None

The options for this command

None
nsfw bool

This command should only work in NSFW channels

False

Returns:

Type Description
Callable[[AsyncCallable], SlashCommand]

A SlashCommand object

Source code in interactions/models/internal/application_commands.py
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
def subcommand(
    base: str | LocalisedName,
    *,
    subcommand_group: Optional[str | LocalisedName] = None,
    name: Absent[str | LocalisedName] = MISSING,
    description: Absent[str | LocalisedDesc] = MISSING,
    base_description: Optional[str | LocalisedDesc] = None,
    base_desc: Optional[str | LocalisedDesc] = None,
    base_default_member_permissions: Optional["Permissions"] = None,
    base_integration_types: Optional[List[Union[IntegrationType, int]]] = None,
    base_contexts: Optional[List[Union[ContextType, int]]] = None,
    base_dm_permission: bool = True,
    subcommand_group_description: Optional[str | LocalisedDesc] = None,
    sub_group_desc: Optional[str | LocalisedDesc] = None,
    scopes: List["Snowflake_Type"] | None = None,
    options: List[dict] | None = None,
    nsfw: bool = False,
) -> Callable[[AsyncCallable], SlashCommand]:
    """
    A decorator specifically tailored for creating subcommands.

    Args:
        base: The name of the base command
        subcommand_group: The name of the subcommand group, if any.
        name: The name of the subcommand, defaults to the name of the coroutine.
        description: The description of the subcommand
        base_description: The description of the base command
        base_desc: An alias of `base_description`
        base_default_member_permissions: What permissions members need to have by default to use this command.
        base_integration_types: Installation context(s) where the command is available, only for globally-scoped commands.
        base_contexts: Interaction context(s) where the command can be used, only for globally-scoped commands.
        base_dm_permission: Should this command be available in DMs (deprecated).
        subcommand_group_description: Description of the subcommand group
        sub_group_desc: An alias for `subcommand_group_description`
        scopes: The scopes of which this command is available, defaults to GLOBAL_SCOPE
        options: The options for this command
        nsfw: This command should only work in NSFW channels

    Returns:
        A SlashCommand object

    """

    def wrapper(func: AsyncCallable) -> SlashCommand:
        if not asyncio.iscoroutinefunction(func):
            raise ValueError("Commands must be coroutines")

        _name = name
        if _name is MISSING:
            _name = func.__name__

        _description = description
        if _description is MISSING:
            _description = func.__doc__ or "No Description Set"

        cmd = SlashCommand(
            name=base,
            description=(base_description or base_desc) or "No Description Set",
            group_name=subcommand_group,
            group_description=(subcommand_group_description or sub_group_desc) or "No Description Set",
            sub_cmd_name=_name,
            sub_cmd_description=_description,
            default_member_permissions=base_default_member_permissions,
            integration_types=base_integration_types or [IntegrationType.GUILD_INSTALL],
            contexts=base_contexts or [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL],
            dm_permission=base_dm_permission,
            scopes=scopes or [GLOBAL_SCOPE],
            callback=func,
            options=options,
            nsfw=nsfw,
        )
        return cmd

    return wrapper

sync_needed(local_cmd, remote_cmd=None)

Compares a local application command to its remote counterpart to determine if a sync is required.

Parameters:

Name Type Description Default
local_cmd dict

The local json representation of the command

required
remote_cmd Optional[dict]

The json representation of the command from Discord

None

Returns:

Type Description
bool

Boolean indicating if a sync is needed

Source code in interactions/models/internal/application_commands.py
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
def sync_needed(local_cmd: dict, remote_cmd: Optional[dict] = None) -> bool:
    """
    Compares a local application command to its remote counterpart to determine if a sync is required.

    Args:
        local_cmd: The local json representation of the command
        remote_cmd: The json representation of the command from Discord

    Returns:
        Boolean indicating if a sync is needed

    """
    if not remote_cmd:
        # No remote version, command must be new
        return True

    if not _compare_commands(local_cmd, remote_cmd):
        # basic comparison of attributes
        return True

    if remote_cmd["type"] == CommandType.CHAT_INPUT:
        try:
            if not _compare_options(local_cmd["options"], remote_cmd["options"]):
                # options are not the same, sync needed
                return True
        except KeyError:
            if "options" in local_cmd or "options" in remote_cmd:
                return True

    return False

user_context_menu(name=MISSING, *, scopes=MISSING, default_member_permissions=None, integration_types=None, contexts=None, dm_permission=True)

A decorator to declare a coroutine as a User Context Menu.

Parameters:

Name Type Description Default
name Absent[str | LocalisedName]

1-32 character name of the context menu, defaults to the name of the coroutine.

MISSING
scopes Absent[List[Snowflake_Type]]

The scope this command exists within

MISSING
default_member_permissions Optional[Permissions]

What permissions members need to have by default to use this command.

None
integration_types Optional[List[Union[IntegrationType, int]]]

Installation context(s) where the command is available, only for globally-scoped commands.

None
contexts Optional[List[Union[ContextType, int]]]

Interaction context(s) where the command can be used, only for globally-scoped commands.

None
dm_permission bool

Should this command be available in DMs (deprecated).

True

Returns:

Type Description
Callable[[AsyncCallable], ContextMenu]

ContextMenu object

Source code in interactions/models/internal/application_commands.py
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
def user_context_menu(
    name: Absent[str | LocalisedName] = MISSING,
    *,
    scopes: Absent[List["Snowflake_Type"]] = MISSING,
    default_member_permissions: Optional["Permissions"] = None,
    integration_types: Optional[List[Union[IntegrationType, int]]] = None,
    contexts: Optional[List[Union[ContextType, int]]] = None,
    dm_permission: bool = True,
) -> Callable[[AsyncCallable], ContextMenu]:
    """
    A decorator to declare a coroutine as a User Context Menu.

    Args:
        name: 1-32 character name of the context menu, defaults to the name of the coroutine.
        scopes: The scope this command exists within
        default_member_permissions: What permissions members need to have by default to use this command.
        integration_types: Installation context(s) where the command is available, only for globally-scoped commands.
        contexts: Interaction context(s) where the command can be used, only for globally-scoped commands.
        dm_permission: Should this command be available in DMs (deprecated).

    Returns:
        ContextMenu object

    """
    return context_menu(
        name,
        context_type=CommandType.USER,
        scopes=scopes,
        default_member_permissions=default_member_permissions,
        integration_types=integration_types or [IntegrationType.GUILD_INSTALL],
        contexts=contexts or [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL],
        dm_permission=dm_permission,
    )

Context

AutocompleteContext

Bases: BaseInteractionContext[ClientT]

Source code in interactions/models/internal/context.py
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
class AutocompleteContext(BaseInteractionContext[ClientT]):
    focussed_option: SlashCommandOption  # todo: option parsing
    """The option the user is currently filling in."""

    @classmethod
    def from_dict(cls, client: "ClientT", payload: dict) -> Self:
        return super().from_dict(client, payload)

    @property
    def input_text(self) -> str:
        """The text the user has already filled in."""
        return self.kwargs.get(str(self.focussed_option.name), "")

    def option_processing_hook(self, option: dict) -> None:
        if option.get("focused", False):
            self.focussed_option = SlashCommandOption.from_dict(option)
        return

    async def send(
        self, choices: typing.Iterable[str | int | float | dict[str, int | float | str] | SlashCommandChoice]
    ) -> None:
        """
        Send your autocomplete choices to discord. Choices must be either a list of strings, or a dictionary following the following format:

        ```json
            {
              "name": str,
              "value": str
            }
        ```
        Where name is the text visible in Discord, and value is the data sent back to your client when that choice is
        chosen.

        Args:
            choices: 25 choices the user can pick

        """
        if self.focussed_option.type == OptionType.STRING:
            type_cast = str
        elif self.focussed_option.type == OptionType.INTEGER:
            type_cast = int
        elif self.focussed_option.type == OptionType.NUMBER:
            type_cast = float
        else:
            type_cast = None

        processed_choices = []
        for choice in choices:
            if isinstance(choice, dict):
                name = choice["name"]
                value = choice["value"]
            elif isinstance(choice, SlashCommandChoice):
                name = choice.name.get_locale(self.locale)
                value = choice.value
            else:
                name = str(choice)
                value = choice

            processed_choices.append({"name": name, "value": type_cast(value) if type_cast else value})

        payload = {"type": CallbackType.AUTOCOMPLETE_RESULT, "data": {"choices": processed_choices}}
        await self.client.http.post_initial_response(payload, self.id, self.token)

focussed_option: SlashCommandOption class-attribute

The option the user is currently filling in.

input_text: str property

The text the user has already filled in.

send(choices) async

Send your autocomplete choices to discord. Choices must be either a list of strings, or a dictionary following the following format:

1
2
3
4
    {
      "name": str,
      "value": str
    }
Where name is the text visible in Discord, and value is the data sent back to your client when that choice is chosen.

Parameters:

Name Type Description Default
choices typing.Iterable[str | int | float | dict[str, int | float | str] | SlashCommandChoice]

25 choices the user can pick

required
Source code in interactions/models/internal/context.py
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
async def send(
    self, choices: typing.Iterable[str | int | float | dict[str, int | float | str] | SlashCommandChoice]
) -> None:
    """
    Send your autocomplete choices to discord. Choices must be either a list of strings, or a dictionary following the following format:

    ```json
        {
          "name": str,
          "value": str
        }
    ```
    Where name is the text visible in Discord, and value is the data sent back to your client when that choice is
    chosen.

    Args:
        choices: 25 choices the user can pick

    """
    if self.focussed_option.type == OptionType.STRING:
        type_cast = str
    elif self.focussed_option.type == OptionType.INTEGER:
        type_cast = int
    elif self.focussed_option.type == OptionType.NUMBER:
        type_cast = float
    else:
        type_cast = None

    processed_choices = []
    for choice in choices:
        if isinstance(choice, dict):
            name = choice["name"]
            value = choice["value"]
        elif isinstance(choice, SlashCommandChoice):
            name = choice.name.get_locale(self.locale)
            value = choice.value
        else:
            name = str(choice)
            value = choice

        processed_choices.append({"name": name, "value": type_cast(value) if type_cast else value})

    payload = {"type": CallbackType.AUTOCOMPLETE_RESULT, "data": {"choices": processed_choices}}
    await self.client.http.post_initial_response(payload, self.id, self.token)

BaseContext

Bases: typing.Generic[ClientT]

Base context class for all contexts.

Define your own context class by inheriting from this class. For compatibility with the library, you must define a from_dict classmethod that takes a dict and returns an instance of your context class.

Source code in interactions/models/internal/context.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
class BaseContext(typing.Generic[ClientT], metaclass=abc.ABCMeta):
    """
    Base context class for all contexts.

    Define your own context class by inheriting from this class. For compatibility with the library, you must define a `from_dict` classmethod that takes a dict and returns an instance of your context class.

    """

    command: BaseCommand
    """The command this context invokes."""

    author_id: Snowflake
    """The id of the user that invoked this context."""
    channel_id: Snowflake
    """The id of the channel this context was invoked in."""
    message_id: Snowflake
    """The id of the message that invoked this context."""

    guild_id: typing.Optional[Snowflake]
    """The id of the guild this context was invoked in, if any."""

    def __init__(self, client: ClientT) -> None:
        self.client: ClientT = client
        """The client that created this context."""

        self.author_id = MISSING
        self.channel_id = MISSING
        self.message_id = MISSING
        self.guild_id = MISSING

    @property
    def guild(self) -> typing.Optional["interactions.Guild"]:
        """The guild this context was invoked in."""
        return self.client.cache.get_guild(self.guild_id)

    @property
    def user(self) -> "interactions.User":
        """The user that invoked this context."""
        return self.client.cache.get_user(self.author_id)

    @property
    def member(self) -> typing.Optional["interactions.Member"]:
        """The member object that invoked this context."""
        return self.client.cache.get_member(self.guild_id, self.author_id)

    @property
    def author(self) -> "interactions.Member | interactions.User":
        """The member or user that invoked this context."""
        return self.member or self.user

    @property
    def channel(self) -> "interactions.TYPE_MESSAGEABLE_CHANNEL":
        """The channel this context was invoked in."""
        if self.guild_id:
            return self.client.cache.get_channel(self.channel_id)
        return self.client.cache.get_dm_channel(self.author_id)

    @property
    def message(self) -> typing.Optional["interactions.Message"]:
        """The message that invoked this context, if any."""
        return self.client.cache.get_message(self.channel_id, self.message_id)

    @property
    def voice_state(self) -> typing.Optional["interactions.VoiceState"]:
        """The current voice state of the bot in the guild this context was invoked in, if any."""
        return self.client.cache.get_bot_voice_state(self.guild_id)

    @property
    def bot(self) -> "ClientT":
        return self.client

    @classmethod
    @abc.abstractmethod
    def from_dict(cls, client: "ClientT", payload: dict) -> Self:
        """
        Create a context instance from a dict.

        Args:
            client: The client creating this context.
            payload: The dict to create the context from.

        Returns:
            The context instance.

        """
        raise NotImplementedError

author: interactions.Member | interactions.User property

The member or user that invoked this context.

channel: interactions.TYPE_MESSAGEABLE_CHANNEL property

The channel this context was invoked in.

client: ClientT = client instance-attribute

The client that created this context.

command: BaseCommand class-attribute

The command this context invokes.

guild: typing.Optional[interactions.Guild] property

The guild this context was invoked in.

member: typing.Optional[interactions.Member] property

The member object that invoked this context.

message: typing.Optional[interactions.Message] property

The message that invoked this context, if any.

user: interactions.User property

The user that invoked this context.

voice_state: typing.Optional[interactions.VoiceState] property

The current voice state of the bot in the guild this context was invoked in, if any.

from_dict(client, payload) classmethod abstractmethod

Create a context instance from a dict.

Parameters:

Name Type Description Default
client ClientT

The client creating this context.

required
payload dict

The dict to create the context from.

required

Returns:

Type Description
Self

The context instance.

Source code in interactions/models/internal/context.py
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
@classmethod
@abc.abstractmethod
def from_dict(cls, client: "ClientT", payload: dict) -> Self:
    """
    Create a context instance from a dict.

    Args:
        client: The client creating this context.
        payload: The dict to create the context from.

    Returns:
        The context instance.

    """
    raise NotImplementedError

BaseInteractionContext

Bases: BaseContext[ClientT]

Source code in interactions/models/internal/context.py
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
class BaseInteractionContext(BaseContext[ClientT]):
    token: str
    """The interaction token."""
    id: Snowflake
    """The interaction ID."""

    locale: str
    """The selected locale of the invoking user (https://discord.com/developers/docs/reference#locales)"""
    guild_locale: str
    """The selected locale of the invoking user's guild (https://discord.com/developers/docs/reference#locales)"""
    resolved: Resolved
    """The resolved data for this interaction."""

    # state info
    deferred: bool
    """Whether the interaction has been deferred."""
    responded: bool
    """Whether the interaction has been responded to."""
    ephemeral: bool
    """Whether the interaction response is ephemeral."""

    authorizing_integration_owners: dict[IntegrationType, Snowflake]
    """Mapping of installation contexts that the interaction was authorized for to related user or guild IDs"""
    context: typing.Optional[ContextType]
    """Context where the interaction was triggered from"""

    entitlements: list[Entitlement]
    """The entitlements of the invoking user."""

    _context_type: int
    """The type of the interaction."""
    command_id: Snowflake
    """The command ID of the interaction."""
    _command_name: str
    """The command name of the interaction."""

    permission_map: dict[Snowflake, Permissions]

    args: list[typing.Any]
    """The arguments passed to the interaction."""
    kwargs: dict[str, typing.Any]
    """The keyword arguments passed to the interaction."""

    def __init__(self, client: "ClientT") -> None:
        super().__init__(client)
        self.deferred = False
        self.responded = False
        self.ephemeral = False

    @classmethod
    def from_dict(cls, client: "ClientT", payload: dict) -> Self:
        instance = cls(client=client)
        instance.token = payload["token"]
        instance.id = Snowflake(payload["id"])
        instance.permission_map = {client.app.id: Permissions(payload.get("app_permissions", 0))}
        instance.locale = payload["locale"]
        instance.guild_locale = payload.get("guild_locale", instance.locale)
        instance._context_type = payload.get("type", 0)
        instance.resolved = Resolved.from_dict(client, payload["data"].get("resolved", {}), payload.get("guild_id"))
        instance.entitlements = Entitlement.from_list(payload.get("entitlements", []), client)
        instance.context = ContextType(payload["context"]) if payload.get("context") else None
        instance.authorizing_integration_owners = {
            IntegrationType(int(integration_type)): Snowflake(owner_id)
            for integration_type, owner_id in payload.get("authorizing_integration_owners", {}).items()
        }

        instance.channel_id = Snowflake(payload["channel_id"])
        if channel := payload.get("channel"):
            client.cache.place_channel_data(channel)

        if member := payload.get("member"):
            instance.author_id = Snowflake(member["user"]["id"])
            instance.guild_id = Snowflake(payload["guild_id"])
            client.cache.place_member_data(instance.guild_id, member)
        else:
            instance.author_id = Snowflake(payload["user"]["id"])
            client.cache.place_user_data(payload["user"])

        if message_data := payload.get("message"):
            message = client.cache.place_message_data(message_data)
            instance.message_id = message.id

        instance.guild_id = to_optional_snowflake(payload.get("guild_id"))

        if payload["type"] in (InteractionType.APPLICATION_COMMAND, InteractionType.AUTOCOMPLETE):
            instance.command_id = Snowflake(payload["data"]["id"])
            instance._command_name = payload["data"]["name"]

        instance.process_options(payload)

        if member := payload.get("member"):
            instance.permission_map[Snowflake(member["id"])] = Permissions(member["permissions"])

        return instance

    @property
    def app_permissions(self) -> Permissions:
        """The permissions available to this interaction"""
        return self.permission_map.get(self.client.app.id, Permissions(0))

    @property
    def author_permissions(self) -> Permissions:
        """The permissions available to the author of this interaction"""
        if self.guild_id:
            return self.permission_map.get(self.author_id, Permissions(0))
        return Permissions(0)

    @property
    def command(self) -> typing.Optional[InteractionCommand]:
        return self.client._interaction_lookup.get(self._command_name)

    @property
    def expires_at(self) -> Timestamp:
        """The time at which the interaction expires."""
        if self.responded or self.deferred:
            return self.id.created_at + datetime.timedelta(minutes=15)
        return self.id.created_at + datetime.timedelta(seconds=3)

    @property
    def expired(self) -> bool:
        """Whether the interaction has expired."""
        return Timestamp.utcnow() > self.expires_at

    @property
    def invoke_target(self) -> str:
        """The invoke target of the interaction."""
        return self._command_name

    @property
    def deferred_ephemeral(self) -> bool:
        """Whether the interaction has been deferred ephemerally."""
        return self.deferred and self.ephemeral

    def option_processing_hook(self, option: dict) -> typing.Any:
        """
        Hook for extending options processing.

        This is called for each option, before the library processes it. If this returns a value, the library will not process the option further.

        Args:
            option: The option to process.

        Returns:
            The processed option.

        """
        return option

    def process_options(self, data: discord_typings.InteractionCallbackData) -> None:
        if data["type"] not in (InteractionType.APPLICATION_COMMAND, InteractionType.AUTOCOMPLETE):
            self.args = []
            self.kwargs = {}
            return

        def gather_options(_options: list[dict[str, typing.Any]]) -> dict[str, typing.Any]:
            """Recursively gather options from an option list."""
            kwargs = {}
            for option in _options:
                if hook_result := self.option_processing_hook(option):
                    kwargs[option["name"]] = hook_result

                if option["type"] in (OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP):
                    self._command_name = f"{self._command_name} {option['name']}"
                    return gather_options(option["options"])

                value = option.get("value")

                if option["type"] in OptionType.resolvable_types():
                    value = self.resolved.get(value, value)

                kwargs[option["name"]] = value
            return kwargs

        if options := data["data"].get("options"):
            self.kwargs = gather_options(options)  # type: ignore
        else:
            self.kwargs = {}
        self.args = list(self.kwargs.values())

app_permissions: Permissions property

The permissions available to this interaction

args: list[typing.Any] class-attribute

The arguments passed to the interaction.

author_permissions: Permissions property

The permissions available to the author of this interaction

authorizing_integration_owners: dict[IntegrationType, Snowflake] class-attribute

Mapping of installation contexts that the interaction was authorized for to related user or guild IDs

command_id: Snowflake class-attribute

The command ID of the interaction.

context: typing.Optional[ContextType] class-attribute

Context where the interaction was triggered from

deferred_ephemeral: bool property

Whether the interaction has been deferred ephemerally.

entitlements: list[Entitlement] class-attribute

The entitlements of the invoking user.

expired: bool property

Whether the interaction has expired.

expires_at: Timestamp property

The time at which the interaction expires.

guild_locale: str class-attribute

The selected locale of the invoking user's guild (https://discord.com/developers/docs/reference#locales)

id: Snowflake class-attribute

The interaction ID.

invoke_target: str property

The invoke target of the interaction.

kwargs: dict[str, typing.Any] class-attribute

The keyword arguments passed to the interaction.

locale: str class-attribute

The selected locale of the invoking user (https://discord.com/developers/docs/reference#locales)

resolved: Resolved class-attribute

The resolved data for this interaction.

token: str class-attribute

The interaction token.

option_processing_hook(option)

Hook for extending options processing.

This is called for each option, before the library processes it. If this returns a value, the library will not process the option further.

Parameters:

Name Type Description Default
option dict

The option to process.

required

Returns:

Type Description
typing.Any

The processed option.

Source code in interactions/models/internal/context.py
373
374
375
376
377
378
379
380
381
382
383
384
385
386
def option_processing_hook(self, option: dict) -> typing.Any:
    """
    Hook for extending options processing.

    This is called for each option, before the library processes it. If this returns a value, the library will not process the option further.

    Args:
        option: The option to process.

    Returns:
        The processed option.

    """
    return option

ComponentContext

Bases: InteractionContext[ClientT], ModalMixin

Source code in interactions/models/internal/context.py
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
class ComponentContext(InteractionContext[ClientT], ModalMixin):
    values: list[str]
    """The values of the SelectMenu component, if any."""
    custom_id: str
    """The custom_id of the component."""
    component_type: int
    """The type of the component."""
    defer_edit_origin: bool
    """Whether you have deferred the interaction and are editing the original response."""

    @classmethod
    def from_dict(cls, client: "ClientT", payload: dict) -> Self:
        instance = super().from_dict(client, payload)
        instance.values = payload["data"].get("values", [])
        instance.custom_id = payload["data"]["custom_id"]
        instance._command_id = instance.custom_id
        instance._command_name = instance.custom_id
        instance.component_type = payload["data"]["component_type"]
        instance.defer_edit_origin = False

        searches = {
            "users": instance.component_type in (ComponentType.USER_SELECT, ComponentType.MENTIONABLE_SELECT),
            "members": instance.guild_id
            and instance.component_type in (ComponentType.USER_SELECT, ComponentType.MENTIONABLE_SELECT),
            "channels": instance.component_type in (ComponentType.CHANNEL_SELECT, ComponentType.MENTIONABLE_SELECT),
            "roles": instance.guild_id
            and instance.component_type in (ComponentType.ROLE_SELECT, ComponentType.MENTIONABLE_SELECT),
        }

        if instance.component_type in (
            ComponentType.USER_SELECT,
            ComponentType.CHANNEL_SELECT,
            ComponentType.ROLE_SELECT,
            ComponentType.MENTIONABLE_SELECT,
        ):
            for i, value in enumerate(instance.values):
                if re.match(r"\d{17,}", value):
                    key = Snowflake(value)

                    if resolved := instance.resolved.get(key):
                        instance.values[i] = resolved
                    elif searches["members"] and (member := instance.client.cache.get_member(instance.guild_id, key)):
                        instance.values[i] = member
                    elif searches["users"] and (user := instance.client.cache.get_user(key)):
                        instance.values[i] = user
                    elif searches["roles"] and (role := instance.client.cache.get_role(key)):
                        instance.values[i] = role
                    elif searches["channels"] and (channel := instance.client.cache.get_channel(key)):
                        instance.values[i] = channel
        return instance

    async def defer(self, *, ephemeral: bool = False, edit_origin: bool = False, suppress_error: bool = False) -> None:
        """
        Defer the interaction.

        Note:
            This method's ephemeral settings override the ephemeral settings of `send()`.

            For example, deferring with `ephemeral=True` will make the response ephemeral even with
            `send(ephemeral=False)`.

        Args:
            ephemeral: Whether the interaction response should be ephemeral.
            edit_origin: Whether to edit the original message instead of sending a new one.
            suppress_error: Should errors on deferring be suppressed than raised.

        """
        if suppress_error:
            with contextlib.suppress(AlreadyDeferred, AlreadyResponded, HTTPException):
                await self._defer(ephemeral=ephemeral, edit_origin=edit_origin)
        else:
            await self._defer(ephemeral=ephemeral, edit_origin=edit_origin)

    async def _defer(self, *, ephemeral: bool = False, edit_origin: bool = False) -> None:
        if self.deferred:
            raise AlreadyDeferred("Interaction has already been responded to.")
        if self.responded:
            raise AlreadyResponded("Interaction has already been responded to.")

        payload = {
            "type": (
                CallbackType.DEFERRED_UPDATE_MESSAGE
                if edit_origin
                else CallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
            )
        }
        if ephemeral:
            if edit_origin:
                raise ValueError("Cannot use ephemeral and edit_origin together.")
            payload["data"] = {"flags": MessageFlags.EPHEMERAL}

        await self.client.http.post_initial_response(payload, self.id, self.token)
        self.deferred = True
        self.ephemeral = ephemeral
        self.editing_origin = edit_origin

    async def edit_origin(
        self,
        *,
        content: typing.Optional[str] = None,
        embeds: typing.Optional[
            typing.Union[typing.Iterable[typing.Union["Embed", dict]], typing.Union["Embed", dict]]
        ] = None,
        embed: typing.Optional[typing.Union["Embed", dict]] = None,
        components: typing.Optional[
            typing.Union[
                typing.Iterable[typing.Iterable[typing.Union["BaseComponent", dict]]],
                typing.Iterable[typing.Union["BaseComponent", dict]],
                "BaseComponent",
                dict,
            ]
        ] = None,
        allowed_mentions: typing.Optional[typing.Union["AllowedMentions", dict]] = None,
        files: typing.Optional[typing.Union["UPLOADABLE_TYPE", typing.Iterable["UPLOADABLE_TYPE"]]] = None,
        file: typing.Optional["UPLOADABLE_TYPE"] = None,
        tts: bool = False,
    ) -> "Message":
        """
        Edits the original message of the component.

        Args:
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            allowed_mentions: Allowed mentions for the message.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.

        Returns:
            The message after it was edited.

        """
        if not self.responded and not self.deferred and (files or file):
            # Discord doesn't allow files at initial response, so we defer then edit.
            await self.defer(edit_origin=True)

        message_payload = process_message_payload(
            content=content,
            embeds=embed if embeds is None else embeds,
            components=components,
            allowed_mentions=allowed_mentions,
            tts=tts,
        )

        message_data = None
        if self.deferred:
            if not self.editing_origin:
                get_logger().warning(
                    "If you want to edit the original message, and need to defer, you must set the `edit_origin` kwarg to True!"
                )

            message_data = await self.client.http.edit_interaction_message(
                message_payload, self.client.app.id, self.token, files=file if files is None else files
            )
            self.deferred = False
            self.editing_origin = False
        else:
            payload = {"type": CallbackType.UPDATE_MESSAGE, "data": message_payload}
            await self.client.http.post_initial_response(
                payload, str(self.id), self.token, files=file if files is None else files
            )
            message_data = await self.client.http.get_interaction_message(self.client.app.id, self.token)

        if message_data:
            message = self.client.cache.place_message_data(message_data)
            self.message_id = message.id
            self.responded = True
            return message

    @property
    def component(self) -> typing.Optional[BaseComponent]:
        """The component that was interacted with."""
        if self.message is None or self.message.components is None:
            return None
        for action_row in self.message.components:
            for component in action_row.components:
                if component.custom_id == self.custom_id:
                    return component

component: typing.Optional[BaseComponent] property

The component that was interacted with.

component_type: int class-attribute

The type of the component.

custom_id: str class-attribute

The custom_id of the component.

defer_edit_origin: bool class-attribute

Whether you have deferred the interaction and are editing the original response.

values: list[str] class-attribute

The values of the SelectMenu component, if any.

defer(*, ephemeral=False, edit_origin=False, suppress_error=False) async

Defer the interaction.

Note

This method's ephemeral settings override the ephemeral settings of send().

For example, deferring with ephemeral=True will make the response ephemeral even with send(ephemeral=False).

Parameters:

Name Type Description Default
ephemeral bool

Whether the interaction response should be ephemeral.

False
edit_origin bool

Whether to edit the original message instead of sending a new one.

False
suppress_error bool

Should errors on deferring be suppressed than raised.

False
Source code in interactions/models/internal/context.py
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
async def defer(self, *, ephemeral: bool = False, edit_origin: bool = False, suppress_error: bool = False) -> None:
    """
    Defer the interaction.

    Note:
        This method's ephemeral settings override the ephemeral settings of `send()`.

        For example, deferring with `ephemeral=True` will make the response ephemeral even with
        `send(ephemeral=False)`.

    Args:
        ephemeral: Whether the interaction response should be ephemeral.
        edit_origin: Whether to edit the original message instead of sending a new one.
        suppress_error: Should errors on deferring be suppressed than raised.

    """
    if suppress_error:
        with contextlib.suppress(AlreadyDeferred, AlreadyResponded, HTTPException):
            await self._defer(ephemeral=ephemeral, edit_origin=edit_origin)
    else:
        await self._defer(ephemeral=ephemeral, edit_origin=edit_origin)

edit_origin(*, content=None, embeds=None, embed=None, components=None, allowed_mentions=None, files=None, file=None, tts=False) async

Edits the original message of the component.

Parameters:

Name Type Description Default
content typing.Optional[str]

Message text content.

None
embeds typing.Optional[typing.Union[typing.Iterable[typing.Union[Embed, dict]], typing.Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed typing.Optional[typing.Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
components typing.Optional[typing.Union[typing.Iterable[typing.Iterable[typing.Union[BaseComponent, dict]]], typing.Iterable[typing.Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
allowed_mentions typing.Optional[typing.Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
files typing.Optional[typing.Union[UPLOADABLE_TYPE, typing.Iterable[UPLOADABLE_TYPE]]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
file typing.Optional[UPLOADABLE_TYPE]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False

Returns:

Type Description
Message

The message after it was edited.

Source code in interactions/models/internal/context.py
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
async def edit_origin(
    self,
    *,
    content: typing.Optional[str] = None,
    embeds: typing.Optional[
        typing.Union[typing.Iterable[typing.Union["Embed", dict]], typing.Union["Embed", dict]]
    ] = None,
    embed: typing.Optional[typing.Union["Embed", dict]] = None,
    components: typing.Optional[
        typing.Union[
            typing.Iterable[typing.Iterable[typing.Union["BaseComponent", dict]]],
            typing.Iterable[typing.Union["BaseComponent", dict]],
            "BaseComponent",
            dict,
        ]
    ] = None,
    allowed_mentions: typing.Optional[typing.Union["AllowedMentions", dict]] = None,
    files: typing.Optional[typing.Union["UPLOADABLE_TYPE", typing.Iterable["UPLOADABLE_TYPE"]]] = None,
    file: typing.Optional["UPLOADABLE_TYPE"] = None,
    tts: bool = False,
) -> "Message":
    """
    Edits the original message of the component.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        allowed_mentions: Allowed mentions for the message.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.

    Returns:
        The message after it was edited.

    """
    if not self.responded and not self.deferred and (files or file):
        # Discord doesn't allow files at initial response, so we defer then edit.
        await self.defer(edit_origin=True)

    message_payload = process_message_payload(
        content=content,
        embeds=embed if embeds is None else embeds,
        components=components,
        allowed_mentions=allowed_mentions,
        tts=tts,
    )

    message_data = None
    if self.deferred:
        if not self.editing_origin:
            get_logger().warning(
                "If you want to edit the original message, and need to defer, you must set the `edit_origin` kwarg to True!"
            )

        message_data = await self.client.http.edit_interaction_message(
            message_payload, self.client.app.id, self.token, files=file if files is None else files
        )
        self.deferred = False
        self.editing_origin = False
    else:
        payload = {"type": CallbackType.UPDATE_MESSAGE, "data": message_payload}
        await self.client.http.post_initial_response(
            payload, str(self.id), self.token, files=file if files is None else files
        )
        message_data = await self.client.http.get_interaction_message(self.client.app.id, self.token)

    if message_data:
        message = self.client.cache.place_message_data(message_data)
        self.message_id = message.id
        self.responded = True
        return message

ContextMenuContext

Bases: InteractionContext[ClientT], ModalMixin

Source code in interactions/models/internal/context.py
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
class ContextMenuContext(InteractionContext[ClientT], ModalMixin):
    target_id: Snowflake
    """The id of the target of the context menu."""
    editing_origin: bool
    """Whether you have deferred the interaction and are editing the original response."""
    target_type: None | CommandType
    """The type of the target of the context menu."""

    def __init__(self, client: "ClientT") -> None:
        super().__init__(client)
        self.editing_origin = False

    @classmethod
    def from_dict(cls, client: "ClientT", payload: dict) -> Self:
        instance = super().from_dict(client, payload)
        instance.target_id = Snowflake(payload["data"]["target_id"])
        instance.target_type = CommandType(payload["data"]["type"])
        return instance

    async def defer(self, *, ephemeral: bool = False, edit_origin: bool = False, suppress_error: bool = False) -> None:
        """
        Defer the interaction.

        Note:
            This method's ephemeral settings override the ephemeral settings of `send()`.

            For example, deferring with `ephemeral=True` will make the response ephemeral even with
            `send(ephemeral=False)`.

        Args:
            ephemeral: Whether the interaction response should be ephemeral.
            edit_origin: Whether to edit the original message instead of sending a new one.
            suppress_error: Should errors on deferring be suppressed than raised.

        """
        if suppress_error:
            with contextlib.suppress(AlreadyDeferred, AlreadyResponded, HTTPException):
                await self._defer(ephemeral=ephemeral, edit_origin=edit_origin)
        else:
            await self._defer(ephemeral=ephemeral, edit_origin=edit_origin)

    async def _defer(self, *, ephemeral: bool = False, edit_origin: bool = False) -> None:
        if self.deferred:
            raise AlreadyDeferred("Interaction has already been responded to.")
        if self.responded:
            raise AlreadyResponded("Interaction has already been responded to.")

        payload = {
            "type": (
                CallbackType.DEFERRED_UPDATE_MESSAGE
                if edit_origin
                else CallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
            )
        }
        if ephemeral:
            if edit_origin:
                raise ValueError("Cannot use ephemeral and edit_origin together.")
            payload["data"] = {"flags": MessageFlags.EPHEMERAL}

        await self.client.http.post_initial_response(payload, self.id, self.token)
        self.deferred = True
        self.ephemeral = ephemeral
        self.editing_origin = edit_origin

    @property
    def target(self) -> None | Message | User | Member:
        """
        The target of the context menu.

        Returns:
            The target of the context menu.

        """
        return self.resolved.get(self.target_id)

target: None | Message | User | Member property

The target of the context menu.

Returns:

Type Description
None | Message | User | Member

The target of the context menu.

target_id: Snowflake class-attribute

The id of the target of the context menu.

target_type: None | CommandType class-attribute

The type of the target of the context menu.

defer(*, ephemeral=False, edit_origin=False, suppress_error=False) async

Defer the interaction.

Note

This method's ephemeral settings override the ephemeral settings of send().

For example, deferring with ephemeral=True will make the response ephemeral even with send(ephemeral=False).

Parameters:

Name Type Description Default
ephemeral bool

Whether the interaction response should be ephemeral.

False
edit_origin bool

Whether to edit the original message instead of sending a new one.

False
suppress_error bool

Should errors on deferring be suppressed than raised.

False
Source code in interactions/models/internal/context.py
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
async def defer(self, *, ephemeral: bool = False, edit_origin: bool = False, suppress_error: bool = False) -> None:
    """
    Defer the interaction.

    Note:
        This method's ephemeral settings override the ephemeral settings of `send()`.

        For example, deferring with `ephemeral=True` will make the response ephemeral even with
        `send(ephemeral=False)`.

    Args:
        ephemeral: Whether the interaction response should be ephemeral.
        edit_origin: Whether to edit the original message instead of sending a new one.
        suppress_error: Should errors on deferring be suppressed than raised.

    """
    if suppress_error:
        with contextlib.suppress(AlreadyDeferred, AlreadyResponded, HTTPException):
            await self._defer(ephemeral=ephemeral, edit_origin=edit_origin)
    else:
        await self._defer(ephemeral=ephemeral, edit_origin=edit_origin)

InteractionContext

Bases: BaseInteractionContext[ClientT], SendMixin

Source code in interactions/models/internal/context.py
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
class InteractionContext(BaseInteractionContext[ClientT], SendMixin):
    async def defer(self, *, ephemeral: bool = False, suppress_error: bool = False) -> None:
        """
        Defer the interaction.

        Note:
            This method's ephemeral settings override the ephemeral settings of `send()`.

            For example, deferring with `ephemeral=True` will make the response ephemeral even with
            `send(ephemeral=False)`.

        Args:
            ephemeral: Whether the interaction response should be ephemeral.
            suppress_error: Should errors on deferring be suppressed than raised.

        """
        if suppress_error:
            with contextlib.suppress(AlreadyDeferred, AlreadyResponded, HTTPException):
                await self._defer(ephemeral=ephemeral)
        else:
            await self._defer(ephemeral=ephemeral)

    async def _defer(self, *, ephemeral: bool = False) -> None:
        if self.deferred:
            raise AlreadyDeferred("Interaction has already been responded to.")
        if self.responded:
            raise AlreadyResponded("Interaction has already been responded to.")

        payload = {"type": CallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE}
        if ephemeral:
            payload["data"] = {"flags": MessageFlags.EPHEMERAL}

        await self.client.http.post_initial_response(payload, self.id, self.token)
        self.deferred = True
        self.ephemeral = ephemeral

    async def send_premium_required(self) -> None:
        """
        Send a premium required response.

        !!! warn
            This response has been deprecated by Discord and will be removed in the future.
            Use a button with the PREMIUM type instead.

        When used, the user will be prompted to subscribe to premium to use this feature.
        Only available for applications with monetization enabled.
        """
        if self.responded:
            raise RuntimeError("Cannot send a premium required response after responding")

        await self.client.http.post_initial_response({"type": 10}, self.id, self.token)
        self.responded = True

    async def _send_http_request(
        self, message_payload: dict, files: typing.Iterable["UPLOADABLE_TYPE"] | None = None
    ) -> dict:
        if const.has_client_feature("FOLLOWUP_INTERACTIONS_FOR_IMAGES") and not self.deferred and not self.responded:
            # experimental bypass for discords broken image proxy
            if embeds := message_payload.get("embeds", {}):
                if any(e.get("image") for e in embeds) or any(e.get("thumbnail") for e in embeds):
                    if MessageFlags.EPHEMERAL in message_payload.get("flags", MessageFlags.NONE):
                        self.ephemeral = True
                    await self.defer(ephemeral=self.ephemeral)

        if self.responded:
            message_data = await self.client.http.post_followup(
                message_payload, self.client.app.id, self.token, files=files
            )
        else:
            if isinstance(message_payload, FormData) and not self.deferred:
                await self.defer(ephemeral=self.ephemeral)
            if self.deferred:
                if const.has_client_feature("FOLLOWUP_INTERACTIONS_FOR_IMAGES"):
                    message_data = await self.client.http.post_followup(
                        message_payload, self.client.app.id, self.token, files=files
                    )
                else:
                    message_data = await self.client.http.edit_interaction_message(
                        message_payload, self.client.app.id, self.token, files=files
                    )
            else:
                payload = {
                    "type": CallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
                    "data": message_payload,
                }
                message_data = await self.client.http.post_initial_response(payload, self.id, self.token, files=files)

        if not message_data:
            try:
                message_data = await self.client.http.get_interaction_message(self.client.app.id, self.token)
            except HTTPException:
                pass

        self.responded = True
        return message_data

    async def send(
        self,
        content: typing.Optional[str] = None,
        *,
        embeds: typing.Optional[
            typing.Union[typing.Iterable[typing.Union["Embed", dict]], typing.Union["Embed", dict]]
        ] = None,
        embed: typing.Optional[typing.Union["Embed", dict]] = None,
        components: typing.Optional[
            typing.Union[
                typing.Iterable[typing.Iterable[typing.Union["BaseComponent", dict]]],
                typing.Iterable[typing.Union["BaseComponent", dict]],
                "BaseComponent",
                dict,
            ]
        ] = None,
        stickers: typing.Optional[
            typing.Union[
                typing.Iterable[typing.Union["Sticker", "Snowflake_Type"]],
                "Sticker",
                "Snowflake_Type",
            ]
        ] = None,
        allowed_mentions: typing.Optional[typing.Union["AllowedMentions", dict]] = None,
        reply_to: typing.Optional[typing.Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
        files: typing.Optional[typing.Union["UPLOADABLE_TYPE", typing.Iterable["UPLOADABLE_TYPE"]]] = None,
        file: typing.Optional["UPLOADABLE_TYPE"] = None,
        tts: bool = False,
        suppress_embeds: bool = False,
        silent: bool = False,
        flags: typing.Optional[typing.Union[int, "MessageFlags"]] = None,
        poll: "typing.Optional[Poll | dict]" = None,
        delete_after: typing.Optional[float] = None,
        ephemeral: bool = False,
        **kwargs: typing.Any,
    ) -> "interactions.Message":
        """
        Send a message.

        Args:
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            stickers: IDs of up to 3 stickers in the server to send in the message.
            allowed_mentions: Allowed mentions for the message.
            reply_to: Message to reference, must be from the same channel.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.
            suppress_embeds: Should embeds be suppressed on this send
            silent: Should this message be sent without triggering a notification.
            flags: Message flags to apply.
            poll: A poll.
            delete_after: Delete message after this many seconds.
            ephemeral: Whether the response should be ephemeral

        Returns:
            New message object that was sent.

        """
        flags = MessageFlags(flags or 0)
        if ephemeral:
            flags |= MessageFlags.EPHEMERAL
            self.ephemeral = True
        if suppress_embeds:
            flags |= MessageFlags.SUPPRESS_EMBEDS
        if silent:
            flags |= MessageFlags.SILENT

        return await super().send(
            content=content,
            embeds=embeds,
            embed=embed,
            components=components,
            stickers=stickers,
            allowed_mentions=allowed_mentions,
            reply_to=reply_to,
            files=files,
            file=file,
            tts=tts,
            flags=flags,
            poll=poll,
            delete_after=delete_after,
            pass_self_into_delete=True,
            **kwargs,
        )

    respond = send

    async def delete(self, message: "Snowflake_Type" = "@original") -> None:
        """
        Delete a message sent in response to this interaction.

        Args:
            message: The message to delete. Defaults to @original which represents the initial response message.

        """
        await self.client.http.delete_interaction_message(
            self.client.app.id, self.token, to_snowflake(message) if message != "@original" else message
        )

    async def edit(
        self,
        message: "Snowflake_Type" = "@original",
        *,
        content: typing.Optional[str] = None,
        embeds: typing.Optional[
            typing.Union[typing.Iterable[typing.Union["Embed", dict]], typing.Union["Embed", dict]]
        ] = None,
        embed: typing.Optional[typing.Union["Embed", dict]] = None,
        components: typing.Optional[
            typing.Union[
                typing.Iterable[typing.Iterable[typing.Union["BaseComponent", dict]]],
                typing.Iterable[typing.Union["BaseComponent", dict]],
                "BaseComponent",
                dict,
            ]
        ] = None,
        attachments: typing.Optional[typing.Sequence[Attachment | dict]] = None,
        allowed_mentions: typing.Optional[typing.Union["AllowedMentions", dict]] = None,
        files: typing.Optional[typing.Union["UPLOADABLE_TYPE", typing.Iterable["UPLOADABLE_TYPE"]]] = None,
        file: typing.Optional["UPLOADABLE_TYPE"] = None,
        tts: bool = False,
    ) -> "interactions.Message":
        message_payload = process_message_payload(
            content=content,
            embeds=embed if embeds is None else embeds,
            components=components,
            allowed_mentions=allowed_mentions,
            attachments=attachments,
            tts=tts,
        )

        if file:
            files = [file, *files] if files else [file]
        message_data = await self.client.http.edit_interaction_message(
            payload=message_payload,
            application_id=self.client.app.id,
            token=self.token,
            message_id=to_snowflake(message) if message != "@original" else message,
            files=files,
        )
        if message_data:
            return self.client.cache.place_message_data(message_data)

defer(*, ephemeral=False, suppress_error=False) async

Defer the interaction.

Note

This method's ephemeral settings override the ephemeral settings of send().

For example, deferring with ephemeral=True will make the response ephemeral even with send(ephemeral=False).

Parameters:

Name Type Description Default
ephemeral bool

Whether the interaction response should be ephemeral.

False
suppress_error bool

Should errors on deferring be suppressed than raised.

False
Source code in interactions/models/internal/context.py
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
async def defer(self, *, ephemeral: bool = False, suppress_error: bool = False) -> None:
    """
    Defer the interaction.

    Note:
        This method's ephemeral settings override the ephemeral settings of `send()`.

        For example, deferring with `ephemeral=True` will make the response ephemeral even with
        `send(ephemeral=False)`.

    Args:
        ephemeral: Whether the interaction response should be ephemeral.
        suppress_error: Should errors on deferring be suppressed than raised.

    """
    if suppress_error:
        with contextlib.suppress(AlreadyDeferred, AlreadyResponded, HTTPException):
            await self._defer(ephemeral=ephemeral)
    else:
        await self._defer(ephemeral=ephemeral)

delete(message='@original') async

Delete a message sent in response to this interaction.

Parameters:

Name Type Description Default
message Snowflake_Type

The message to delete. Defaults to @original which represents the initial response message.

'@original'
Source code in interactions/models/internal/context.py
606
607
608
609
610
611
612
613
614
615
616
async def delete(self, message: "Snowflake_Type" = "@original") -> None:
    """
    Delete a message sent in response to this interaction.

    Args:
        message: The message to delete. Defaults to @original which represents the initial response message.

    """
    await self.client.http.delete_interaction_message(
        self.client.app.id, self.token, to_snowflake(message) if message != "@original" else message
    )

send(content=None, *, embeds=None, embed=None, components=None, stickers=None, allowed_mentions=None, reply_to=None, files=None, file=None, tts=False, suppress_embeds=False, silent=False, flags=None, poll=None, delete_after=None, ephemeral=False, **kwargs) async

Send a message.

Parameters:

Name Type Description Default
content typing.Optional[str]

Message text content.

None
embeds typing.Optional[typing.Union[typing.Iterable[typing.Union[Embed, dict]], typing.Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed typing.Optional[typing.Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
components typing.Optional[typing.Union[typing.Iterable[typing.Iterable[typing.Union[BaseComponent, dict]]], typing.Iterable[typing.Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
stickers typing.Optional[typing.Union[typing.Iterable[typing.Union[Sticker, Snowflake_Type]], Sticker, Snowflake_Type]]

IDs of up to 3 stickers in the server to send in the message.

None
allowed_mentions typing.Optional[typing.Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
reply_to typing.Optional[typing.Union[MessageReference, Message, dict, Snowflake_Type]]

Message to reference, must be from the same channel.

None
files typing.Optional[typing.Union[UPLOADABLE_TYPE, typing.Iterable[UPLOADABLE_TYPE]]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
file typing.Optional[UPLOADABLE_TYPE]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False
suppress_embeds bool

Should embeds be suppressed on this send

False
silent bool

Should this message be sent without triggering a notification.

False
flags typing.Optional[typing.Union[int, MessageFlags]]

Message flags to apply.

None
poll Optional[Poll | dict]

A poll.

None
delete_after typing.Optional[float]

Delete message after this many seconds.

None
ephemeral bool

Whether the response should be ephemeral

False

Returns:

Type Description
Message

New message object that was sent.

Source code in interactions/models/internal/context.py
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
async def send(
    self,
    content: typing.Optional[str] = None,
    *,
    embeds: typing.Optional[
        typing.Union[typing.Iterable[typing.Union["Embed", dict]], typing.Union["Embed", dict]]
    ] = None,
    embed: typing.Optional[typing.Union["Embed", dict]] = None,
    components: typing.Optional[
        typing.Union[
            typing.Iterable[typing.Iterable[typing.Union["BaseComponent", dict]]],
            typing.Iterable[typing.Union["BaseComponent", dict]],
            "BaseComponent",
            dict,
        ]
    ] = None,
    stickers: typing.Optional[
        typing.Union[
            typing.Iterable[typing.Union["Sticker", "Snowflake_Type"]],
            "Sticker",
            "Snowflake_Type",
        ]
    ] = None,
    allowed_mentions: typing.Optional[typing.Union["AllowedMentions", dict]] = None,
    reply_to: typing.Optional[typing.Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
    files: typing.Optional[typing.Union["UPLOADABLE_TYPE", typing.Iterable["UPLOADABLE_TYPE"]]] = None,
    file: typing.Optional["UPLOADABLE_TYPE"] = None,
    tts: bool = False,
    suppress_embeds: bool = False,
    silent: bool = False,
    flags: typing.Optional[typing.Union[int, "MessageFlags"]] = None,
    poll: "typing.Optional[Poll | dict]" = None,
    delete_after: typing.Optional[float] = None,
    ephemeral: bool = False,
    **kwargs: typing.Any,
) -> "interactions.Message":
    """
    Send a message.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        stickers: IDs of up to 3 stickers in the server to send in the message.
        allowed_mentions: Allowed mentions for the message.
        reply_to: Message to reference, must be from the same channel.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.
        suppress_embeds: Should embeds be suppressed on this send
        silent: Should this message be sent without triggering a notification.
        flags: Message flags to apply.
        poll: A poll.
        delete_after: Delete message after this many seconds.
        ephemeral: Whether the response should be ephemeral

    Returns:
        New message object that was sent.

    """
    flags = MessageFlags(flags or 0)
    if ephemeral:
        flags |= MessageFlags.EPHEMERAL
        self.ephemeral = True
    if suppress_embeds:
        flags |= MessageFlags.SUPPRESS_EMBEDS
    if silent:
        flags |= MessageFlags.SILENT

    return await super().send(
        content=content,
        embeds=embeds,
        embed=embed,
        components=components,
        stickers=stickers,
        allowed_mentions=allowed_mentions,
        reply_to=reply_to,
        files=files,
        file=file,
        tts=tts,
        flags=flags,
        poll=poll,
        delete_after=delete_after,
        pass_self_into_delete=True,
        **kwargs,
    )

send_premium_required() async

Send a premium required response.

Warn

This response has been deprecated by Discord and will be removed in the future. Use a button with the PREMIUM type instead.

When used, the user will be prompted to subscribe to premium to use this feature. Only available for applications with monetization enabled.

Source code in interactions/models/internal/context.py
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
async def send_premium_required(self) -> None:
    """
    Send a premium required response.

    !!! warn
        This response has been deprecated by Discord and will be removed in the future.
        Use a button with the PREMIUM type instead.

    When used, the user will be prompted to subscribe to premium to use this feature.
    Only available for applications with monetization enabled.
    """
    if self.responded:
        raise RuntimeError("Cannot send a premium required response after responding")

    await self.client.http.post_initial_response({"type": 10}, self.id, self.token)
    self.responded = True

ModalContext

Bases: InteractionContext[ClientT]

Source code in interactions/models/internal/context.py
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
class ModalContext(InteractionContext[ClientT]):
    responses: dict[str, str]
    """The responses of the modal. The key is the `custom_id` of the component."""
    custom_id: str
    """The developer defined custom ID of this modal"""
    edit_origin: bool
    """Whether to edit the original message instead of sending a new one."""

    @classmethod
    def from_dict(cls, client: "ClientT", payload: dict) -> Self:
        instance = super().from_dict(client, payload)
        instance.responses = {
            comp["components"][0]["custom_id"]: comp["components"][0]["value"] for comp in payload["data"]["components"]
        }
        instance.kwargs = instance.responses
        instance.custom_id = payload["data"]["custom_id"]
        instance.edit_origin = False
        return instance

    async def edit(self, message: "Snowflake_Type", **kwargs) -> "interactions.Message":
        if not self.deferred and not self.responded:
            await self.defer(edit_origin=True)
        return await super().edit(message, **kwargs)

    async def defer(self, *, ephemeral: bool = False, edit_origin: bool = False, suppress_error: bool = False) -> None:
        """
        Defer the interaction.

        Note:
            This method's ephemeral settings override the ephemeral settings of `send()`.

            For example, deferring with `ephemeral=True` will make the response ephemeral even with
            `send(ephemeral=False)`.

        Args:
            ephemeral: Whether the interaction response should be ephemeral.
            edit_origin: Whether to edit the original message instead of sending a followup.
            suppress_error: Should errors on deferring be suppressed than raised.

        """
        if suppress_error:
            with contextlib.suppress(AlreadyDeferred, AlreadyResponded, HTTPException):
                await self._defer(ephemeral=ephemeral, edit_origin=edit_origin)
        else:
            await self._defer(ephemeral=ephemeral, edit_origin=edit_origin)

    async def _defer(self, *, ephemeral: bool = False, edit_origin: bool = False) -> None:
        if self.deferred:
            raise AlreadyDeferred("Interaction has already been responded to.")
        if self.responded:
            raise AlreadyResponded("Interaction has already been responded to.")

        payload = {
            "type": (
                CallbackType.DEFERRED_UPDATE_MESSAGE
                if edit_origin
                else CallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
            )
        }
        if ephemeral:
            payload["data"] = {"flags": MessageFlags.EPHEMERAL}

        if edit_origin:
            self.edit_origin = True

        await self.client.http.post_initial_response(payload, self.id, self.token)
        self.deferred = True
        self.ephemeral = ephemeral

custom_id: str class-attribute

The developer defined custom ID of this modal

edit_origin: bool class-attribute

Whether to edit the original message instead of sending a new one.

responses: dict[str, str] class-attribute

The responses of the modal. The key is the custom_id of the component.

defer(*, ephemeral=False, edit_origin=False, suppress_error=False) async

Defer the interaction.

Note

This method's ephemeral settings override the ephemeral settings of send().

For example, deferring with ephemeral=True will make the response ephemeral even with send(ephemeral=False).

Parameters:

Name Type Description Default
ephemeral bool

Whether the interaction response should be ephemeral.

False
edit_origin bool

Whether to edit the original message instead of sending a followup.

False
suppress_error bool

Should errors on deferring be suppressed than raised.

False
Source code in interactions/models/internal/context.py
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
async def defer(self, *, ephemeral: bool = False, edit_origin: bool = False, suppress_error: bool = False) -> None:
    """
    Defer the interaction.

    Note:
        This method's ephemeral settings override the ephemeral settings of `send()`.

        For example, deferring with `ephemeral=True` will make the response ephemeral even with
        `send(ephemeral=False)`.

    Args:
        ephemeral: Whether the interaction response should be ephemeral.
        edit_origin: Whether to edit the original message instead of sending a followup.
        suppress_error: Should errors on deferring be suppressed than raised.

    """
    if suppress_error:
        with contextlib.suppress(AlreadyDeferred, AlreadyResponded, HTTPException):
            await self._defer(ephemeral=ephemeral, edit_origin=edit_origin)
    else:
        await self._defer(ephemeral=ephemeral, edit_origin=edit_origin)

Resolved

A class representing the resolved data from an interaction.

Attributes:

Name Type Description
channels dict[Snowflake, TYPE_MESSAGEABLE_CHANNEL]

A dictionary of channels resolved from the interaction.

members dict[Snowflake, Member]

A dictionary of members resolved from the interaction.

users dict[Snowflake, User]

A dictionary of users resolved from the interaction.

roles dict[Snowflake, Role]

A dictionary of roles resolved from the interaction.

messages dict[Snowflake, Message]

A dictionary of messages resolved from the interaction.

attachments dict[Snowflake, Attachment]

A dictionary of attachments resolved from the interaction.

Source code in interactions/models/internal/context.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
class Resolved:
    """
    A class representing the resolved data from an interaction.

    Attributes:
        channels: A dictionary of channels resolved from the interaction.
        members: A dictionary of members resolved from the interaction.
        users: A dictionary of users resolved from the interaction.
        roles: A dictionary of roles resolved from the interaction.
        messages: A dictionary of messages resolved from the interaction.
        attachments: A dictionary of attachments resolved from the interaction.

    """

    def __init__(self) -> None:
        self.channels: dict[Snowflake, "interactions.TYPE_MESSAGEABLE_CHANNEL"] = {}
        self.members: dict[Snowflake, "interactions.Member"] = {}
        self.users: dict[Snowflake, "interactions.User"] = {}
        self.roles: dict[Snowflake, "interactions.Role"] = {}
        self.messages: dict[Snowflake, "interactions.Message"] = {}
        self.attachments: dict[Snowflake, "interactions.Attachment"] = {}

    def __bool__(self) -> bool:
        """Returns whether any resolved data is present."""
        return (
            bool(self.channels)
            or bool(self.members)
            or bool(self.users)
            or bool(self.roles)
            or bool(self.messages)
            or bool(self.attachments)
        )

    def get(self, snowflake: Snowflake | str, default: typing.Any = None) -> typing.Any:
        snowflake = Snowflake(snowflake)
        """Returns the value of the given snowflake."""
        if channel := self.channels.get(snowflake):
            return channel
        if member := self.members.get(snowflake):
            return member
        if user := self.users.get(snowflake):
            return user
        if role := self.roles.get(snowflake):
            return role
        if message := self.messages.get(snowflake):
            return message
        if attachment := self.attachments.get(snowflake):
            return attachment
        return default

    @classmethod
    def from_dict(cls, client: "interactions.Client", data: dict, guild_id: None | Snowflake = None) -> Self:
        instance = cls()

        if channels := data.get("channels"):
            for key, _channel in channels.items():
                instance.channels[Snowflake(key)] = client.cache.place_channel_data(_channel)

        if members := data.get("members"):
            for key, _member in members.items():
                instance.members[Snowflake(key)] = client.cache.place_member_data(
                    guild_id, {**_member, "user": {**data["users"][key]}}
                )

        if users := data.get("users"):
            for key, _user in users.items():
                instance.users[Snowflake(key)] = client.cache.place_user_data(_user)

        if roles := data.get("roles"):
            for key, _role in roles.items():
                instance.roles[Snowflake(key)] = client.cache.get_role(Snowflake(key))

        if messages := data.get("messages"):
            for key, _msg in messages.items():
                instance.messages[Snowflake(key)] = client.cache.place_message_data(_msg)

        if attachments := data.get("attachments"):
            for key, _attach in attachments.items():
                instance.attachments[Snowflake(key)] = Attachment.from_dict(_attach, client)

        return instance

__bool__()

Returns whether any resolved data is present.

Source code in interactions/models/internal/context.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def __bool__(self) -> bool:
    """Returns whether any resolved data is present."""
    return (
        bool(self.channels)
        or bool(self.members)
        or bool(self.users)
        or bool(self.roles)
        or bool(self.messages)
        or bool(self.attachments)
    )

Presence

Activity

Bases: DictSerializationMixin

Represents a discord activity object use for rich presence in discord.

Source code in interactions/models/discord/activity.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class Activity(DictSerializationMixin):
    """Represents a discord activity object use for rich presence in discord."""

    name: str = attrs.field(repr=True)
    """The activity's name"""
    type: ActivityType = attrs.field(repr=True, default=ActivityType.GAME)
    """The type of activity"""
    url: Optional[str] = attrs.field(repr=True, default=None)
    """Stream url, is validated when type is 1"""
    created_at: Optional[Timestamp] = attrs.field(repr=True, default=None, converter=optional(timestamp_converter))
    """When the activity was added to the user's session"""
    timestamps: Optional[ActivityTimestamps] = attrs.field(
        repr=False, default=None, converter=optional(ActivityTimestamps.from_dict)
    )
    """Start and/or end of the game"""
    application_id: "Snowflake_Type" = attrs.field(repr=False, default=None)
    """Application id for the game"""
    details: Optional[str] = attrs.field(repr=False, default=None)
    """What the player is currently doing"""
    state: Optional[str] = attrs.field(repr=False, default=None)
    """The user's current party status, or text used for a custom status if type is set as CUSTOM"""
    emoji: Optional[PartialEmoji] = attrs.field(repr=False, default=None, converter=optional(PartialEmoji.from_dict))
    """The emoji used for a custom status"""
    party: Optional[ActivityParty] = attrs.field(repr=False, default=None, converter=optional(ActivityParty.from_dict))
    """Information for the current party of the player"""
    assets: Optional[ActivityAssets] = attrs.field(
        repr=False, default=None, converter=optional(ActivityAssets.from_dict)
    )
    """Assets to display on the player's profile"""
    secrets: Optional[ActivitySecrets] = attrs.field(
        repr=False, default=None, converter=optional(ActivitySecrets.from_dict)
    )
    """Secrets for Rich Presence joining and spectating"""
    instance: Optional[bool] = attrs.field(repr=False, default=False)
    """Whether or not the activity is an instanced game session"""
    flags: Optional[ActivityFlag] = attrs.field(repr=False, default=None, converter=optional(ActivityFlag))
    """Activity flags bitwise OR together, describes what the payload includes"""
    buttons: List[str] = attrs.field(repr=False, factory=list)
    """The custom buttons shown in the Rich Presence (max 2)"""

    @classmethod
    def create(
        cls, name: str, type: ActivityType = ActivityType.GAME, url: Optional[str] = None, state: Optional[str] = None
    ) -> "Activity":
        """
        Creates an activity object for the bot.

        Args:
            name: The new activity's name
            type: Type of activity to create
            url: Stream link for the activity
            state: Current party status, or text used for a custom status if type is set as CUSTOM

        Returns:
            The new activity object

        """
        return cls(name=name, type=type, url=url, state=state)

    def to_dict(self) -> dict:
        return dict_filter_none({"name": self.name, "type": self.type, "state": self.state, "url": self.url})

application_id: Snowflake_Type = attrs.field(repr=False, default=None) class-attribute

Application id for the game

assets: Optional[ActivityAssets] = attrs.field(repr=False, default=None, converter=optional(ActivityAssets.from_dict)) class-attribute

Assets to display on the player's profile

buttons: List[str] = attrs.field(repr=False, factory=list) class-attribute

The custom buttons shown in the Rich Presence (max 2)

created_at: Optional[Timestamp] = attrs.field(repr=True, default=None, converter=optional(timestamp_converter)) class-attribute

When the activity was added to the user's session

details: Optional[str] = attrs.field(repr=False, default=None) class-attribute

What the player is currently doing

emoji: Optional[PartialEmoji] = attrs.field(repr=False, default=None, converter=optional(PartialEmoji.from_dict)) class-attribute

The emoji used for a custom status

flags: Optional[ActivityFlag] = attrs.field(repr=False, default=None, converter=optional(ActivityFlag)) class-attribute

Activity flags bitwise OR together, describes what the payload includes

instance: Optional[bool] = attrs.field(repr=False, default=False) class-attribute

Whether or not the activity is an instanced game session

name: str = attrs.field(repr=True) class-attribute

The activity's name

party: Optional[ActivityParty] = attrs.field(repr=False, default=None, converter=optional(ActivityParty.from_dict)) class-attribute

Information for the current party of the player

secrets: Optional[ActivitySecrets] = attrs.field(repr=False, default=None, converter=optional(ActivitySecrets.from_dict)) class-attribute

Secrets for Rich Presence joining and spectating

state: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The user's current party status, or text used for a custom status if type is set as CUSTOM

timestamps: Optional[ActivityTimestamps] = attrs.field(repr=False, default=None, converter=optional(ActivityTimestamps.from_dict)) class-attribute

Start and/or end of the game

type: ActivityType = attrs.field(repr=True, default=ActivityType.GAME) class-attribute

The type of activity

url: Optional[str] = attrs.field(repr=True, default=None) class-attribute

Stream url, is validated when type is 1

create(name, type=ActivityType.GAME, url=None, state=None) classmethod

Creates an activity object for the bot.

Parameters:

Name Type Description Default
name str

The new activity's name

required
type ActivityType

Type of activity to create

ActivityType.GAME
url Optional[str]

Stream link for the activity

None
state Optional[str]

Current party status, or text used for a custom status if type is set as CUSTOM

None

Returns:

Type Description
Activity

The new activity object

Source code in interactions/models/discord/activity.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
@classmethod
def create(
    cls, name: str, type: ActivityType = ActivityType.GAME, url: Optional[str] = None, state: Optional[str] = None
) -> "Activity":
    """
    Creates an activity object for the bot.

    Args:
        name: The new activity's name
        type: Type of activity to create
        url: Stream link for the activity
        state: Current party status, or text used for a custom status if type is set as CUSTOM

    Returns:
        The new activity object

    """
    return cls(name=name, type=type, url=url, state=state)

ActivityAssets

Bases: DictSerializationMixin

Source code in interactions/models/discord/activity.py
38
39
40
41
42
43
44
45
46
47
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ActivityAssets(DictSerializationMixin):
    large_image: Optional[str] = attrs.field(repr=False, default=None)
    """The large image for this activity. Uses discord's asset image url format."""
    large_text: Optional[str] = attrs.field(repr=False, default=None)
    """Hover text for the large image"""
    small_image: Optional[str] = attrs.field(repr=False, default=None)
    """The large image for this activity. Uses discord's asset image url format."""
    small_text: Optional[str] = attrs.field(repr=False, default=None)
    """Hover text for the small image"""

large_image: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The large image for this activity. Uses discord's asset image url format.

large_text: Optional[str] = attrs.field(repr=False, default=None) class-attribute

Hover text for the large image

small_image: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The large image for this activity. Uses discord's asset image url format.

small_text: Optional[str] = attrs.field(repr=False, default=None) class-attribute

Hover text for the small image

ActivityParty

Bases: DictSerializationMixin

Source code in interactions/models/discord/activity.py
30
31
32
33
34
35
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ActivityParty(DictSerializationMixin):
    id: Optional[str] = attrs.field(repr=False, default=None)
    """A unique identifier for this party"""
    size: Optional[List[int]] = attrs.field(repr=False, default=None)
    """Info about the size of the party"""

id: Optional[str] = attrs.field(repr=False, default=None) class-attribute

A unique identifier for this party

size: Optional[List[int]] = attrs.field(repr=False, default=None) class-attribute

Info about the size of the party

ActivitySecrets

Bases: DictSerializationMixin

Source code in interactions/models/discord/activity.py
50
51
52
53
54
55
56
57
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ActivitySecrets(DictSerializationMixin):
    join: Optional[str] = attrs.field(repr=False, default=None)
    """The secret for joining a party"""
    spectate: Optional[str] = attrs.field(repr=False, default=None)
    """The secret for spectating a party"""
    match: Optional[str] = attrs.field(repr=False, default=None)
    """The secret for a specific instanced match"""

join: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The secret for joining a party

match: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The secret for a specific instanced match

spectate: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The secret for spectating a party

ActivityTimestamps

Bases: DictSerializationMixin

Source code in interactions/models/discord/activity.py
22
23
24
25
26
27
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ActivityTimestamps(DictSerializationMixin):
    start: Optional[Timestamp] = attrs.field(repr=False, default=None, converter=optional(timestamp_converter))
    """The start time of the activity. Shows "elapsed" timer on discord client."""
    end: Optional[Timestamp] = attrs.field(repr=False, default=None, converter=optional(timestamp_converter))
    """The end time of the activity. Shows "remaining" timer on discord client."""

end: Optional[Timestamp] = attrs.field(repr=False, default=None, converter=optional(timestamp_converter)) class-attribute

The end time of the activity. Shows "remaining" timer on discord client.

start: Optional[Timestamp] = attrs.field(repr=False, default=None, converter=optional(timestamp_converter)) class-attribute

The start time of the activity. Shows "elapsed" timer on discord client.


Data

Application

Bases: DiscordObject

Represents a discord application.

Source code in interactions/models/discord/application.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class Application(DiscordObject):
    """Represents a discord application."""

    name: str = attrs.field(repr=True)
    """The name of the application"""
    icon: Optional[Asset] = attrs.field(repr=False, default=None)
    """The icon of the application"""
    description: Optional[str] = attrs.field(repr=False, default=None)
    """The description of the application"""
    rpc_origins: Optional[List[str]] = attrs.field(repr=False, default=None)
    """An array of rpc origin urls, if rpc is enabled"""
    bot_public: bool = attrs.field(repr=False, default=True)
    """When false only app owner can join the app's bot to guilds"""
    bot_require_code_grant: bool = attrs.field(repr=False, default=False)
    """When true the app's bot will only join upon completion of the full oauth2 code grant flow"""
    terms_of_service_url: Optional[str] = attrs.field(repr=False, default=None)
    """The url of the app's terms of service"""
    privacy_policy_url: Optional[str] = attrs.field(repr=False, default=None)
    """The url of the app's privacy policy"""
    owner_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None, converter=optional(to_snowflake))
    """The id of the owner of the application"""
    summary: str = attrs.field(
        repr=False,
    )
    """If this application is a game sold on Discord, this field will be the summary field for the store page of its primary sku"""
    verify_key: Optional[str] = attrs.field(repr=False, default=MISSING)
    """The hex encoded key for verification in interactions and the GameSDK's GetTicket"""
    team: Optional["Team"] = attrs.field(repr=False, default=None)
    """If the application belongs to a team, this will be a list of the members of that team"""
    guild_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None)
    """If this application is a game sold on Discord, this field will be the guild to which it has been linked"""
    primary_sku_id: Optional["Snowflake_Type"] = attrs.field(repr=False, default=None)
    """If this application is a game sold on Discord, this field will be the id of the "Game SKU" that is created, if exists"""
    slug: Optional[str] = attrs.field(repr=False, default=None)
    """If this application is a game sold on Discord, this field will be the URL slug that links to the store page"""
    cover_image: Optional[Asset] = attrs.field(repr=False, default=None)
    """The application's default rich presence invite cover"""
    flags: Optional["ApplicationFlags"] = attrs.field(repr=False, default=None, converter=optional(ApplicationFlags))
    """The application's public flags"""
    tags: Optional[List[str]] = attrs.field(repr=False, default=None)
    """The application's tags describing its functionality and content"""
    # todo: implement an ApplicationInstallParams object. See https://discord.com/developers/docs/resources/application#install-params-object
    install_params: Optional[dict] = attrs.field(repr=False, default=None)
    """The application's settings for in-app invitation to guilds"""
    # todo: implement IntegrationTypeConfigurationObject too, see https://discord.com/developers/docs/resources/application#application-object-application-integration-type-configuration-object
    integration_types_config: Optional[dict] = attrs.field(repr=False, default=None)
    """Default scopes and permissions for each supported installation context. Value for each key is an integration type configuration object"""
    custom_install_url: Optional[str] = attrs.field(repr=False, default=None)
    """The application's custom authorization link for invitation to a guild"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if data.get("team"):
            data["team"] = Team.from_dict(data["team"], client)
            data["owner_id"] = data["team"].owner_user_id
        elif "owner" in data:
            owner = client.cache.place_user_data(data.pop("owner"))
            data["owner_id"] = owner.id

        if data.get("icon"):
            data["icon"] = Asset.from_path_hash(client, f"app-icons/{data['id']}/{{}}", data["icon"])
        if data.get("cover_image"):
            data["cover_image"] = Asset.from_path_hash(client, f"app-icons/{data['id']}/{{}}", data["cover_image"])

        return data

    @property
    def owner(self) -> "User":
        """The user object for the owner of this application"""
        return self._client.cache.get_user(self.owner_id)

bot_public: bool = attrs.field(repr=False, default=True) class-attribute

When false only app owner can join the app's bot to guilds

bot_require_code_grant: bool = attrs.field(repr=False, default=False) class-attribute

When true the app's bot will only join upon completion of the full oauth2 code grant flow

cover_image: Optional[Asset] = attrs.field(repr=False, default=None) class-attribute

The application's default rich presence invite cover

custom_install_url: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The application's custom authorization link for invitation to a guild

description: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The description of the application

flags: Optional[ApplicationFlags] = attrs.field(repr=False, default=None, converter=optional(ApplicationFlags)) class-attribute

The application's public flags

guild_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

If this application is a game sold on Discord, this field will be the guild to which it has been linked

icon: Optional[Asset] = attrs.field(repr=False, default=None) class-attribute

The icon of the application

install_params: Optional[dict] = attrs.field(repr=False, default=None) class-attribute

The application's settings for in-app invitation to guilds

integration_types_config: Optional[dict] = attrs.field(repr=False, default=None) class-attribute

Default scopes and permissions for each supported installation context. Value for each key is an integration type configuration object

name: str = attrs.field(repr=True) class-attribute

The name of the application

owner: User property

The user object for the owner of this application

owner_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None, converter=optional(to_snowflake)) class-attribute

The id of the owner of the application

primary_sku_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

If this application is a game sold on Discord, this field will be the id of the "Game SKU" that is created, if exists

privacy_policy_url: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The url of the app's privacy policy

rpc_origins: Optional[List[str]] = attrs.field(repr=False, default=None) class-attribute

An array of rpc origin urls, if rpc is enabled

slug: Optional[str] = attrs.field(repr=False, default=None) class-attribute

If this application is a game sold on Discord, this field will be the URL slug that links to the store page

summary: str = attrs.field(repr=False) class-attribute

If this application is a game sold on Discord, this field will be the summary field for the store page of its primary sku

tags: Optional[List[str]] = attrs.field(repr=False, default=None) class-attribute

The application's tags describing its functionality and content

team: Optional[Team] = attrs.field(repr=False, default=None) class-attribute

If the application belongs to a team, this will be a list of the members of that team

terms_of_service_url: Optional[str] = attrs.field(repr=False, default=None) class-attribute

The url of the app's terms of service

verify_key: Optional[str] = attrs.field(repr=False, default=MISSING) class-attribute

The hex encoded key for verification in interactions and the GameSDK's GetTicket

Team

Bases: DiscordObject

Source code in interactions/models/discord/team.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class Team(DiscordObject):
    icon: Optional[Asset] = attrs.field(repr=False, default=None)
    """A hash of the image of the team's icon"""
    members: List[TeamMember] = attrs.field(repr=False, factory=list)
    """The members of the team"""
    name: str = attrs.field(repr=True)
    """The name of the team"""
    owner_user_id: "Snowflake_Type" = attrs.field(repr=False, converter=to_snowflake)
    """The user id of the current team owner"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data["members"] = TeamMember.from_list(data["members"], client)
        if data["icon"]:
            data["icon"] = Asset.from_path_hash(client, f"team-icons/{data['id']}/{{}}", data["icon"])
        return data

    @property
    def owner(self) -> "User":
        """The owner of the team"""
        return self._client.cache.get_user(self.owner_user_id)

    def is_in_team(self, user: Union["SnowflakeObject", "Snowflake_Type"]) -> bool:
        """
        Returns True if the passed user or ID is a member within the team.

        Args:
            user: The user or user ID to check

        Returns:
            Boolean indicating whether the user is in the team

        """
        return to_snowflake(user) in [m.id for m in self.members]

icon: Optional[Asset] = attrs.field(repr=False, default=None) class-attribute

A hash of the image of the team's icon

members: List[TeamMember] = attrs.field(repr=False, factory=list) class-attribute

The members of the team

name: str = attrs.field(repr=True) class-attribute

The name of the team

owner: User property

The owner of the team

owner_user_id: Snowflake_Type = attrs.field(repr=False, converter=to_snowflake) class-attribute

The user id of the current team owner

is_in_team(user)

Returns True if the passed user or ID is a member within the team.

Parameters:

Name Type Description Default
user Union[SnowflakeObject, Snowflake_Type]

The user or user ID to check

required

Returns:

Type Description
bool

Boolean indicating whether the user is in the team

Source code in interactions/models/discord/team.py
60
61
62
63
64
65
66
67
68
69
70
71
def is_in_team(self, user: Union["SnowflakeObject", "Snowflake_Type"]) -> bool:
    """
    Returns True if the passed user or ID is a member within the team.

    Args:
        user: The user or user ID to check

    Returns:
        Boolean indicating whether the user is in the team

    """
    return to_snowflake(user) in [m.id for m in self.members]

TeamMember

Bases: DiscordObject

Source code in interactions/models/discord/team.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class TeamMember(DiscordObject):
    membership_state: TeamMembershipState = attrs.field(repr=False, converter=TeamMembershipState)
    """Rhe user's membership state on the team"""
    # permissions: List[str] = attrs.field(repr=False, default=["*"])  # disabled until discord adds more team roles
    team_id: "Snowflake_Type" = attrs.field(repr=True)
    """Rhe id of the parent team of which they are a member"""
    user: "User" = attrs.field(
        repr=False,
    )  # TODO: cache partial user (avatar, discrim, id, username)
    """Rhe avatar, discriminator, id, and username of the user"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data["user"] = client.cache.place_user_data(data["user"])
        data["id"] = data["user"].id
        return data

membership_state: TeamMembershipState = attrs.field(repr=False, converter=TeamMembershipState) class-attribute

Rhe user's membership state on the team

team_id: Snowflake_Type = attrs.field(repr=True) class-attribute

Rhe id of the parent team of which they are a member

user: User = attrs.field(repr=False) class-attribute

Rhe avatar, discriminator, id, and username of the user

ActivityType

Bases: CursedIntEnum

The types of presence activity that can be used in presences.

Note

Only GAME STREAMING LISTENING WATCHING and COMPETING are usable by bots

Source code in interactions/models/discord/enums.py
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
class ActivityType(CursedIntEnum):
    """
    The types of presence activity that can be used in presences.

    !!! note
        Only `GAME` `STREAMING` `LISTENING` `WATCHING` and `COMPETING` are usable by bots

    """

    GAME = 0
    """Playing {name}; Example: Playing Rocket League"""
    STREAMING = 1
    """Streaming {details}; Example: Streaming Rocket League"""
    LISTENING = 2
    """Listening to {name}; Example: Listening to Spotify"""
    WATCHING = 3
    """Watching {name}; Example: Watching YouTube Together"""
    CUSTOM = 4
    """{emoji} {name}; Example: `:smiley: I am cool`"""
    COMPETING = 5
    """Competing in {name}; Example: Competing in Arena World Champions"""

    PLAYING = GAME
    """Alias for `GAME`"""

COMPETING = 5 class-attribute

Competing in {name}; Example: Competing in Arena World Champions

CUSTOM = 4 class-attribute

{emoji} {name}; Example: :smiley: I am cool

GAME = 0 class-attribute

Playing {name}; Example: Playing Rocket League

LISTENING = 2 class-attribute

Listening to {name}; Example: Listening to Spotify

PLAYING = GAME class-attribute

Alias for GAME

STREAMING = 1 class-attribute

Streaming {details}; Example: Streaming Rocket League

WATCHING = 3 class-attribute

Watching {name}; Example: Watching YouTube Together

ApplicationFlags

Bases: DiscordIntFlag

Flags an application can have.

Source code in interactions/models/discord/enums.py
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
class ApplicationFlags(DiscordIntFlag):  # type: ignore
    """Flags an application can have."""

    # Flags defined by the Discord API
    GATEWAY_PRESENCE = 1 << 12
    """Verified to use presence intent"""
    GATEWAY_PRESENCE_LIMITED = 1 << 13
    """Using presence intent, without verification"""
    GATEWAY_GUILD_MEMBERS = 1 << 14
    """Verified to use guild members intent"""
    GATEWAY_GUILD_MEMBERS_LIMITED = 1 << 15
    """Using members intent, without verification"""
    VERIFICATION_PENDING_GUILD_LIMIT = 1 << 16
    """Bot has hit guild limit, and has not been successfully verified"""
    EMBEDDED = 1 << 17
    """Application is a voice channel activity (ie YouTube Together)"""

EMBEDDED = 1 << 17 class-attribute

Application is a voice channel activity (ie YouTube Together)

GATEWAY_GUILD_MEMBERS = 1 << 14 class-attribute

Verified to use guild members intent

GATEWAY_GUILD_MEMBERS_LIMITED = 1 << 15 class-attribute

Using members intent, without verification

GATEWAY_PRESENCE = 1 << 12 class-attribute

Verified to use presence intent

GATEWAY_PRESENCE_LIMITED = 1 << 13 class-attribute

Using presence intent, without verification

VERIFICATION_PENDING_GUILD_LIMIT = 1 << 16 class-attribute

Bot has hit guild limit, and has not been successfully verified

AuditLogEventType

Bases: CursedIntEnum

The type of audit log entry type

ref: https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events

Source code in interactions/models/discord/enums.py
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
class AuditLogEventType(CursedIntEnum):
    """
    The type of audit log entry type

    ref: https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events
    """

    GUILD_UPDATE = 1
    CHANNEL_CREATE = 10
    CHANNEL_UPDATE = 11
    CHANNEL_DELETE = 12
    CHANNEL_OVERWRITE_CREATE = 13
    CHANNEL_OVERWRITE_UPDATE = 14
    CHANNEL_OVERWRITE_DELETE = 15
    MEMBER_KICK = 20
    MEMBER_PRUNE = 21
    MEMBER_BAN_ADD = 22
    MEMBER_BAN_REMOVE = 23
    MEMBER_UPDATE = 24
    MEMBER_ROLE_UPDATE = 25
    MEMBER_MOVE = 26
    MEMBER_DISCONNECT = 27
    BOT_ADD = 28
    ROLE_CREATE = 30
    ROLE_UPDATE = 31
    ROLE_DELETE = 32
    INVITE_CREATE = 40
    INVITE_UPDATE = 41
    INVITE_DELETE = 42
    WEBHOOK_CREATE = 50
    WEBHOOK_UPDATE = 51
    WEBHOOK_DELETE = 52
    EMOJI_CREATE = 60
    EMOJI_UPDATE = 61
    EMOJI_DELETE = 62
    MESSAGE_DELETE = 72
    MESSAGE_BULK_DELETE = 73
    MESSAGE_PIN = 74
    MESSAGE_UNPIN = 75
    INTEGRATION_CREATE = 80
    INTEGRATION_UPDATE = 81
    INTEGRATION_DELETE = 82
    STAGE_INSTANCE_CREATE = 83
    STAGE_INSTANCE_UPDATE = 84
    STAGE_INSTANCE_DELETE = 85
    STICKER_CREATE = 90
    STICKER_UPDATE = 91
    STICKER_DELETE = 92
    GUILD_SCHEDULED_EVENT_CREATE = 100
    GUILD_SCHEDULED_EVENT_UPDATE = 101
    GUILD_SCHEDULED_EVENT_DELETE = 102
    THREAD_CREATE = 110
    THREAD_UPDATE = 111
    THREAD_DELETE = 112
    APPLICATION_COMMAND_PERMISSION_UPDATE = 121
    AUTO_MODERATION_RULE_CREATE = 140
    AUTO_MODERATION_RULE_UPDATE = 141
    AUTO_MODERATION_RULE_DELETE = 142
    AUTO_MODERATION_BLOCK_MESSAGE = 143
    AUTO_MODERATION_FLAG_TO_CHANNEL = 144
    AUTO_MODERATION_USER_COMMUNICATION_DISABLED = 145
    AUTO_MODERATION_QUARANTINE = 146
    CREATOR_MONETIZATION_REQUEST_CREATED = 150
    CREATOR_MONETIZATION_TERMS_ACCEPTED = 151
    ROLE_PROMPT_CREATE = 160
    ROLE_PROMPT_UPDATE = 161
    ROLE_PROMPT_DELETE = 162
    ON_BOARDING_QUESTION_CREATE = 163
    ON_BOARDING_QUESTION_UPDATE = 164
    ONBOARDING_UPDATE = 167
    GUILD_HOME_FEATURE_ITEM = 171
    GUILD_HOME_FEATURE_ITEM_UPDATE = 172
    BLOCKED_PHISHING_LINK = 180
    SERVER_GUIDE_CREATE = 190
    SERVER_GUIDE_UPDATE = 191
    VOICE_CHANNEL_STATUS_CREATE = 192
    VOICE_CHANNEL_STATUS_DELETE = 193
    CLYDE_AI_PROFILE_UPDATE = 194
    GUILD_SCHEDULED_EVENT_EXCEPTION_CREATE = 200
    GUILD_SCHEDULED_EVENT_EXCEPTION_UPDATE = 201
    GUILD_SCHEDULED_EVENT_EXCEPTION_DELETE = 202

AutoArchiveDuration

Bases: CursedIntEnum

Thread archive duration, in minutes.

Source code in interactions/models/discord/enums.py
885
886
887
888
889
890
891
class AutoArchiveDuration(CursedIntEnum):
    """Thread archive duration, in minutes."""

    ONE_HOUR = 60
    ONE_DAY = 1440
    THREE_DAY = 4320
    ONE_WEEK = 10080

ButtonStyle

Bases: CursedIntEnum

The styles of buttons supported.

Source code in interactions/models/discord/enums.py
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
class ButtonStyle(CursedIntEnum):
    """The styles of buttons supported."""

    # Based on discord api
    PRIMARY = 1
    """blurple"""
    SECONDARY = 2
    """grey"""
    SUCCESS = 3
    """green"""
    DANGER = 4
    """red"""
    LINK = 5
    """url button"""
    PREMIUM = 6
    """premium button"""

    # Aliases
    BLUE = 1
    BLURPLE = 1
    GRAY = 2
    GREY = 2
    GREEN = 3
    RED = 4
    URL = 5

DANGER = 4 class-attribute

red

url button

PREMIUM = 6 class-attribute

premium button

PRIMARY = 1 class-attribute

blurple

SECONDARY = 2 class-attribute

grey

SUCCESS = 3 class-attribute

green

ChannelFlags

Bases: DiscordIntFlag

Source code in interactions/models/discord/enums.py
864
865
866
867
868
869
870
871
872
873
874
875
class ChannelFlags(DiscordIntFlag):
    PINNED = 1 << 1
    """ Thread is pinned to the top of its parent forum channel """
    REQUIRE_TAG = 1 << 4
    """Whether a tag is required to be specified when creating a thread in a Guild Forum or Media channel."""
    CLYDE_THREAD = 1 << 8
    """This thread was created by Clyde"""
    HIDE_MEDIA_DOWNLOAD_OPTIONS = 1 << 15
    """when set hides the embedded media download options. Available only for media channels"""

    # Special members
    NONE = 0

CLYDE_THREAD = 1 << 8 class-attribute

This thread was created by Clyde

HIDE_MEDIA_DOWNLOAD_OPTIONS = 1 << 15 class-attribute

when set hides the embedded media download options. Available only for media channels

PINNED = 1 << 1 class-attribute

Thread is pinned to the top of its parent forum channel

REQUIRE_TAG = 1 << 4 class-attribute

Whether a tag is required to be specified when creating a thread in a Guild Forum or Media channel.

ChannelType

Bases: CursedIntEnum

Types of channel.

Source code in interactions/models/discord/enums.py
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
class ChannelType(CursedIntEnum):
    """Types of channel."""

    GUILD_TEXT = 0
    """Text channel within a server"""
    DM = 1
    """Direct message between users"""
    GUILD_VOICE = 2
    """Voice channel within a server"""
    GROUP_DM = 3
    """Direct message between multiple users"""
    GUILD_CATEGORY = 4
    """Organizational category that contains up to 50 channels"""
    GUILD_NEWS = 5
    """Channel that users can follow and crosspost into their own server"""
    GUILD_NEWS_THREAD = 10
    """Temporary sub-channel within a GUILD_NEWS channel"""
    GUILD_PUBLIC_THREAD = 11
    """Temporary sub-channel within a GUILD_TEXT channel"""
    GUILD_PRIVATE_THREAD = 12
    """Temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission"""
    GUILD_STAGE_VOICE = 13
    """Voice channel for hosting events with an audience"""
    GUILD_FORUM = 15
    """A Forum channel"""
    GUILD_MEDIA = 16
    """Channel that can only contain threads, similar to `GUILD_FORUM` channels"""

    @property
    def guild(self) -> bool:
        """Whether this channel is a guild channel."""
        return self.value not in {1, 3}

    @property
    def voice(self) -> bool:
        """Whether this channel is a voice channel."""
        return self.value in {2, 13}

DM = 1 class-attribute

Direct message between users

GROUP_DM = 3 class-attribute

Direct message between multiple users

GUILD_CATEGORY = 4 class-attribute

Organizational category that contains up to 50 channels

GUILD_FORUM = 15 class-attribute

A Forum channel

GUILD_MEDIA = 16 class-attribute

Channel that can only contain threads, similar to GUILD_FORUM channels

GUILD_NEWS = 5 class-attribute

Channel that users can follow and crosspost into their own server

GUILD_NEWS_THREAD = 10 class-attribute

Temporary sub-channel within a GUILD_NEWS channel

GUILD_PRIVATE_THREAD = 12 class-attribute

Temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission

GUILD_PUBLIC_THREAD = 11 class-attribute

Temporary sub-channel within a GUILD_TEXT channel

GUILD_STAGE_VOICE = 13 class-attribute

Voice channel for hosting events with an audience

GUILD_TEXT = 0 class-attribute

Text channel within a server

GUILD_VOICE = 2 class-attribute

Voice channel within a server

guild: bool property

Whether this channel is a guild channel.

voice: bool property

Whether this channel is a voice channel.

CommandType

Bases: CursedIntEnum

The interaction commands supported by discord.

Source code in interactions/models/discord/enums.py
701
702
703
704
705
706
707
708
709
class CommandType(CursedIntEnum):
    """The interaction commands supported by discord."""

    CHAT_INPUT = 1
    """Slash commands; a text-based command that shows up when a user types `/`"""
    USER = 2
    """A UI-based command that shows up when you right click or tap on a user"""
    MESSAGE = 3
    """A UI-based command that shows up when you right click or tap on a message"""

CHAT_INPUT = 1 class-attribute

Slash commands; a text-based command that shows up when a user types /

MESSAGE = 3 class-attribute

A UI-based command that shows up when you right click or tap on a message

USER = 2 class-attribute

A UI-based command that shows up when you right click or tap on a user

ComponentType

Bases: CursedIntEnum

The types of components supported by discord.

Source code in interactions/models/discord/enums.py
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
class ComponentType(CursedIntEnum):
    """The types of components supported by discord."""

    ACTION_ROW = 1
    """Container for other components"""
    BUTTON = 2
    """Button object"""
    STRING_SELECT = 3
    """Select menu for picking from text choices"""
    INPUT_TEXT = 4
    """Text input object"""
    USER_SELECT = 5
    """Select menu for picking from users"""
    ROLE_SELECT = 6
    """Select menu for picking from roles"""
    MENTIONABLE_SELECT = 7
    """Select menu for picking from mentionable objects"""
    CHANNEL_SELECT = 8
    """Select menu for picking from channels"""

ACTION_ROW = 1 class-attribute

Container for other components

BUTTON = 2 class-attribute

Button object

CHANNEL_SELECT = 8 class-attribute

Select menu for picking from channels

INPUT_TEXT = 4 class-attribute

Text input object

MENTIONABLE_SELECT = 7 class-attribute

Select menu for picking from mentionable objects

ROLE_SELECT = 6 class-attribute

Select menu for picking from roles

STRING_SELECT = 3 class-attribute

Select menu for picking from text choices

USER_SELECT = 5 class-attribute

Select menu for picking from users

ContextType

Bases: CursedIntEnum

The context of where an interaction can be used.

Source code in interactions/models/discord/enums.py
693
694
695
696
697
698
class ContextType(CursedIntEnum):
    """The context of where an interaction can be used."""

    GUILD = 0
    BOT_DM = 1
    PRIVATE_CHANNEL = 2

CursedIntEnum

Bases: IntEnum

Source code in interactions/models/discord/enums.py
144
145
146
147
148
class CursedIntEnum(IntEnum):
    @classmethod
    def _missing_(cls: Type[SELF], value) -> SELF:
        """Construct a new enum item to represent this new unknown type - without losing the value"""
        return _return_cursed_enum(cls, value)

DefaultNotificationLevel

Bases: CursedIntEnum

Default Notification levels for dms and guilds.

Source code in interactions/models/discord/enums.py
788
789
790
791
792
class DefaultNotificationLevel(CursedIntEnum):
    """Default Notification levels for dms and guilds."""

    ALL_MESSAGES = 0
    ONLY_MENTIONS = 1

EmbedType

Bases: Enum

Types of embed.

Source code in interactions/models/discord/enums.py
440
441
442
443
444
445
446
447
448
449
450
451
class EmbedType(Enum):
    """Types of embed."""

    RICH = "rich"
    IMAGE = "image"
    VIDEO = "video"
    GIFV = "gifv"
    ARTICLE = "article"
    LINK = "link"
    AUTOMOD_MESSAGE = "auto_moderation_message"
    AUTOMOD_NOTIFICATION = "auto_moderation_notification"
    POLL_RESULT = "poll_result"

EntitlementType

Bases: CursedIntEnum

The type of entitlement.

Source code in interactions/models/discord/enums.py
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
class EntitlementType(CursedIntEnum):
    """The type of entitlement."""

    PURCHASE = 1
    """Entitlement was purchased by user"""
    PREMIUM_SUBSCRIPTION = 2
    """Entitlement for Discord Nitro subscription"""
    DEVELOPER_GIFT = 3
    """Entitlement was gifted by developer"""
    TEST_MODE_PURCHASE = 4
    """Entitlement was purchased by a dev in application test mode"""
    FREE_PURCHASE = 5
    """Entitlement was granted when the SKU was free"""
    USER_GIFT = 6
    """Entitlement was gifted by another user"""
    PREMIUM_PURCHASE = 7
    """Entitlement was claimed by user for free as a Nitro Subscriber"""
    APPLICATION_SUBSCRIPTION = 8
    """Entitlement was purchased as an app subscription"""

APPLICATION_SUBSCRIPTION = 8 class-attribute

Entitlement was purchased as an app subscription

DEVELOPER_GIFT = 3 class-attribute

Entitlement was gifted by developer

FREE_PURCHASE = 5 class-attribute

Entitlement was granted when the SKU was free

PREMIUM_PURCHASE = 7 class-attribute

Entitlement was claimed by user for free as a Nitro Subscriber

PREMIUM_SUBSCRIPTION = 2 class-attribute

Entitlement for Discord Nitro subscription

PURCHASE = 1 class-attribute

Entitlement was purchased by user

TEST_MODE_PURCHASE = 4 class-attribute

Entitlement was purchased by a dev in application test mode

USER_GIFT = 6 class-attribute

Entitlement was gifted by another user

ExplicitContentFilterLevel

Bases: CursedIntEnum

Automatic filtering of explicit content.

Source code in interactions/models/discord/enums.py
795
796
797
798
799
800
class ExplicitContentFilterLevel(CursedIntEnum):
    """Automatic filtering of explicit content."""

    DISABLED = 0
    MEMBERS_WITHOUT_ROLES = 1
    ALL_MEMBERS = 2

ForumLayoutType

Bases: CursedIntEnum

The layout of a forum channel.

Source code in interactions/models/discord/enums.py
1121
1122
1123
1124
1125
1126
class ForumLayoutType(CursedIntEnum):
    """The layout of a forum channel."""

    NOT_SET = 0
    LIST = 1
    GALLERY = 2

ForumSortOrder

Bases: CursedIntEnum

The order of a forum channel.

Source code in interactions/models/discord/enums.py
1129
1130
1131
1132
1133
1134
1135
1136
1137
class ForumSortOrder(CursedIntEnum):
    """The order of a forum channel."""

    LATEST_ACTIVITY = 0
    CREATION_DATE = 1

    @classmethod
    def converter(cls, value: Optional[int]) -> "ForumSortOrder":
        return None if value is None else cls(value)

IntegrationType

Bases: CursedIntEnum

The types of installation contexts supported by discord.

Source code in interactions/models/discord/enums.py
686
687
688
689
690
class IntegrationType(CursedIntEnum):
    """The types of installation contexts supported by discord."""

    GUILD_INSTALL = 0
    USER_INSTALL = 1

Intents

Bases: DiscordIntFlag

When identifying to the gateway, you can specify an intents parameter which allows you to conditionally subscribe to pre-defined "intents", groups of events defined by Discord.

info

For details about what intents do, or which intents you'll want, please read the Discord API Documentation

Source code in interactions/models/discord/enums.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
class Intents(DiscordIntFlag):  # type: ignore
    """
    When identifying to the gateway, you can specify an intents parameter which allows you to conditionally subscribe to pre-defined "intents", groups of events defined by Discord.

    info:
        For details about what intents do, or which intents you'll want, please read the [Discord API Documentation](https://discord.com/developers/docs/topics/gateway#gateway-intents)

    """

    GUILDS = 1 << 0
    GUILD_MEMBERS = 1 << 1
    GUILD_MODERATION = 1 << 2
    GUILD_EMOJIS_AND_STICKERS = 1 << 3
    GUILD_INTEGRATIONS = 1 << 4
    GUILD_WEBHOOKS = 1 << 5
    GUILD_INVITES = 1 << 6
    GUILD_VOICE_STATES = 1 << 7
    GUILD_PRESENCES = 1 << 8
    GUILD_MESSAGES = 1 << 9
    GUILD_MESSAGE_REACTIONS = 1 << 10
    GUILD_MESSAGE_TYPING = 1 << 11
    DIRECT_MESSAGES = 1 << 12
    DIRECT_MESSAGE_REACTIONS = 1 << 13
    DIRECT_MESSAGE_TYPING = 1 << 14
    MESSAGE_CONTENT = 1 << 15
    GUILD_SCHEDULED_EVENTS = 1 << 16
    AUTO_MODERATION_CONFIGURATION = 1 << 20
    AUTO_MODERATION_EXECUTION = 1 << 21
    GUILD_MESSAGE_POLLS = 1 << 24
    DIRECT_MESSAGE_POLLS = 1 << 25

    # Shortcuts/grouping/aliases
    MESSAGES = GUILD_MESSAGES | DIRECT_MESSAGES
    REACTIONS = GUILD_MESSAGE_REACTIONS | DIRECT_MESSAGE_REACTIONS
    TYPING = GUILD_MESSAGE_TYPING | DIRECT_MESSAGE_TYPING
    AUTO_MOD = AUTO_MODERATION_CONFIGURATION | AUTO_MODERATION_EXECUTION
    POLLS = GUILD_MESSAGE_POLLS | DIRECT_MESSAGE_POLLS

    PRIVILEGED = GUILD_PRESENCES | GUILD_MEMBERS | MESSAGE_CONTENT
    NON_PRIVILEGED = AntiFlag(PRIVILEGED)
    DEFAULT = NON_PRIVILEGED

    # Special members
    NONE = 0
    ALL = AntiFlag()

    @classmethod
    def new(
        cls,
        guilds=False,
        guild_members=False,
        guild_moderation=False,
        guild_emojis_and_stickers=False,
        guild_integrations=False,
        guild_webhooks=False,
        guild_invites=False,
        guild_voice_states=False,
        guild_presences=False,
        guild_messages=False,
        guild_message_polls=False,
        guild_message_reactions=False,
        guild_message_typing=False,
        direct_messages=False,
        direct_message_polls=False,
        direct_message_reactions=False,
        direct_message_typing=False,
        message_content=False,
        guild_scheduled_events=False,
        messages=False,
        polls=False,
        reactions=False,
        typing=False,
        privileged=False,
        non_privileged=False,
        default=False,
        all=False,
    ) -> "Intents":
        """Set your desired intents."""
        kwargs = locals()
        del kwargs["cls"]

        intents = cls.NONE
        for key in kwargs:
            if kwargs[key]:
                intents |= getattr(cls, key.upper())
        return intents

new(guilds=False, guild_members=False, guild_moderation=False, guild_emojis_and_stickers=False, guild_integrations=False, guild_webhooks=False, guild_invites=False, guild_voice_states=False, guild_presences=False, guild_messages=False, guild_message_polls=False, guild_message_reactions=False, guild_message_typing=False, direct_messages=False, direct_message_polls=False, direct_message_reactions=False, direct_message_typing=False, message_content=False, guild_scheduled_events=False, messages=False, polls=False, reactions=False, typing=False, privileged=False, non_privileged=False, default=False, all=False) classmethod

Set your desired intents.

Source code in interactions/models/discord/enums.py
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
@classmethod
def new(
    cls,
    guilds=False,
    guild_members=False,
    guild_moderation=False,
    guild_emojis_and_stickers=False,
    guild_integrations=False,
    guild_webhooks=False,
    guild_invites=False,
    guild_voice_states=False,
    guild_presences=False,
    guild_messages=False,
    guild_message_polls=False,
    guild_message_reactions=False,
    guild_message_typing=False,
    direct_messages=False,
    direct_message_polls=False,
    direct_message_reactions=False,
    direct_message_typing=False,
    message_content=False,
    guild_scheduled_events=False,
    messages=False,
    polls=False,
    reactions=False,
    typing=False,
    privileged=False,
    non_privileged=False,
    default=False,
    all=False,
) -> "Intents":
    """Set your desired intents."""
    kwargs = locals()
    del kwargs["cls"]

    intents = cls.NONE
    for key in kwargs:
        if kwargs[key]:
            intents |= getattr(cls, key.upper())
    return intents

InteractionPermissionTypes

Bases: CursedIntEnum

The type of interaction permission received by discord.

Source code in interactions/models/discord/enums.py
722
723
724
725
726
727
class InteractionPermissionTypes(CursedIntEnum):
    """The type of interaction permission received by discord."""

    ROLE = 1
    USER = 2
    CHANNEL = 3

InteractionType

Bases: CursedIntEnum

The type of interaction received by discord.

Source code in interactions/models/discord/enums.py
712
713
714
715
716
717
718
719
class InteractionType(CursedIntEnum):
    """The type of interaction received by discord."""

    PING = 1
    APPLICATION_COMMAND = 2
    MESSAGE_COMPONENT = 3
    AUTOCOMPLETE = 4
    MODAL_RESPONSE = 5

MFALevel

Bases: CursedIntEnum

Does the user use 2FA.

Source code in interactions/models/discord/enums.py
803
804
805
806
807
class MFALevel(CursedIntEnum):
    """Does the user use 2FA."""

    NONE = 0
    ELEVATED = 1

MentionType

Bases: str, Enum

Types of mention.

Source code in interactions/models/discord/enums.py
757
758
759
760
761
762
class MentionType(str, Enum):
    """Types of mention."""

    EVERYONE = "everyone"
    ROLES = "roles"
    USERS = "users"

MessageActivityType

Bases: CursedIntEnum

An activity object, similar to an embed.

Source code in interactions/models/discord/enums.py
454
455
456
457
458
459
460
461
462
463
464
class MessageActivityType(CursedIntEnum):
    """An activity object, similar to an embed."""

    JOIN = 1
    """Join the event"""
    SPECTATE = 2
    """Watch the event"""
    LISTEN = 3
    """Listen along to the event"""
    JOIN_REQUEST = 5
    """Asking a user to join the activity"""

JOIN = 1 class-attribute

Join the event

JOIN_REQUEST = 5 class-attribute

Asking a user to join the activity

LISTEN = 3 class-attribute

Listen along to the event

SPECTATE = 2 class-attribute

Watch the event

MessageFlags

Bases: DiscordIntFlag

Message flags.

Source code in interactions/models/discord/enums.py
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
class MessageFlags(DiscordIntFlag):  # type: ignore
    """Message flags."""

    CROSSPOSTED = 1 << 0
    """This message has been published to subscribed channels (via Channel Following)"""
    IS_CROSSPOST = 1 << 1
    """This message originated from a message in another channel (via Channel Following)"""
    SUPPRESS_EMBEDS = 1 << 2
    """Do not include any embeds when serializing this message"""
    SOURCE_MESSAGE_DELETED = 1 << 3
    """The source message for this crosspost has been deleted (via Channel Following)"""
    URGENT = 1 << 4
    """This message came from the urgent message system"""
    HAS_THREAD = 1 << 5
    """This message has an associated thread, with the same id as the message"""
    EPHEMERAL = 1 << 6
    """This message is only visible to the user who invoked the Interaction"""
    LOADING = 1 << 7
    """This message is an Interaction Response and the bot is "thinking"""
    FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8
    """This message failed to mention some roles and add their members to the thread"""
    SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10
    """This message contains a abusive website link, pops up a warning when clicked"""
    SILENT = 1 << 12
    """This message should not trigger push or desktop notifications"""
    VOICE_MESSAGE = 1 << 13
    """This message is a voice message"""

    SUPPRESS_NOTIFICATIONS = SILENT
    """Alias for :attr:`SILENT`"""

    # Special members
    NONE = 0
    ALL = AntiFlag()

CROSSPOSTED = 1 << 0 class-attribute

This message has been published to subscribed channels (via Channel Following)

EPHEMERAL = 1 << 6 class-attribute

This message is only visible to the user who invoked the Interaction

FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8 class-attribute

This message failed to mention some roles and add their members to the thread

HAS_THREAD = 1 << 5 class-attribute

This message has an associated thread, with the same id as the message

IS_CROSSPOST = 1 << 1 class-attribute

This message originated from a message in another channel (via Channel Following)

LOADING = 1 << 7 class-attribute

This message is an Interaction Response and the bot is "thinking

This message contains a abusive website link, pops up a warning when clicked

SILENT = 1 << 12 class-attribute

This message should not trigger push or desktop notifications

SOURCE_MESSAGE_DELETED = 1 << 3 class-attribute

The source message for this crosspost has been deleted (via Channel Following)

SUPPRESS_EMBEDS = 1 << 2 class-attribute

Do not include any embeds when serializing this message

SUPPRESS_NOTIFICATIONS = SILENT class-attribute

Alias for :attr:SILENT

URGENT = 1 << 4 class-attribute

This message came from the urgent message system

VOICE_MESSAGE = 1 << 13 class-attribute

This message is a voice message

MessageType

Bases: CursedIntEnum

Types of message.

Ref: https://discord.com/developers/docs/resources/channel#message-object-message-types

Source code in interactions/models/discord/enums.py
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
class MessageType(CursedIntEnum):
    """
    Types of message.

    Ref: https://discord.com/developers/docs/resources/channel#message-object-message-types
    """

    DEFAULT = 0
    RECIPIENT_ADD = 1
    RECIPIENT_REMOVE = 2
    CALL = 3
    CHANNEL_NAME_CHANGE = 4
    CHANNEL_ICON_CHANGE = 5
    CHANNEL_PINNED_MESSAGE = 6
    GUILD_MEMBER_JOIN = 7
    USER_PREMIUM_GUILD_SUBSCRIPTION = 8
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11
    CHANNEL_FOLLOW_ADD = 12
    GUILD_DISCOVERY_DISQUALIFIED = 14
    GUILD_DISCOVERY_REQUALIFIED = 15
    GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16
    GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17
    THREAD_CREATED = 18
    REPLY = 19
    APPLICATION_COMMAND = 20
    THREAD_STARTER_MESSAGE = 21
    GUILD_INVITE_REMINDER = 22
    CONTEXT_MENU_COMMAND = 23
    AUTO_MODERATION_ACTION = 24
    ROLE_SUBSCRIPTION_PURCHASE = 25
    INTERACTION_PREMIUM_UPSELL = 26
    STAGE_START = 27
    STAGE_END = 28
    STAGE_SPEAKER = 29
    STAGE_TOPIC = 31
    GUILD_APPLICATION_PREMIUM_SUBSCRIPTION = 32
    GUILD_INCIDENT_ALERT_MODE_ENABLED = 36
    GUILD_INCIDENT_ALERT_MODE_DISABLED = 37
    GUILD_INCIDENT_REPORT_RAID = 38
    GUILD_INCIDENT_REPORT_FALSE_ALARM = 39
    PURCHASE_NOTIFICATION = 44

    @classmethod
    def deletable(cls) -> Tuple["MessageType", ...]:
        """Return a tuple of message types that can be deleted."""
        return (
            cls.DEFAULT,
            cls.CHANNEL_PINNED_MESSAGE,
            cls.GUILD_MEMBER_JOIN,
            cls.USER_PREMIUM_GUILD_SUBSCRIPTION,
            cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1,
            cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2,
            cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3,
            cls.CHANNEL_FOLLOW_ADD,
            cls.GUILD_DISCOVERY_DISQUALIFIED,
            cls.GUILD_DISCOVERY_REQUALIFIED,
            cls.GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING,
            cls.GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING,
            cls.THREAD_CREATED,
            cls.REPLY,
            cls.APPLICATION_COMMAND,
            cls.GUILD_INVITE_REMINDER,
            cls.CONTEXT_MENU_COMMAND,
            cls.AUTO_MODERATION_ACTION,
            cls.ROLE_SUBSCRIPTION_PURCHASE,
            cls.INTERACTION_PREMIUM_UPSELL,
            cls.STAGE_START,
            cls.STAGE_END,
            cls.STAGE_SPEAKER,
            cls.STAGE_TOPIC,
            cls.GUILD_APPLICATION_PREMIUM_SUBSCRIPTION,
            cls.GUILD_INCIDENT_ALERT_MODE_ENABLED,
            cls.GUILD_INCIDENT_ALERT_MODE_DISABLED,
            cls.GUILD_INCIDENT_REPORT_RAID,
            cls.GUILD_INCIDENT_REPORT_FALSE_ALARM,
            cls.PURCHASE_NOTIFICATION,
        )

deletable() classmethod

Return a tuple of message types that can be deleted.

Source code in interactions/models/discord/enums.py
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
@classmethod
def deletable(cls) -> Tuple["MessageType", ...]:
    """Return a tuple of message types that can be deleted."""
    return (
        cls.DEFAULT,
        cls.CHANNEL_PINNED_MESSAGE,
        cls.GUILD_MEMBER_JOIN,
        cls.USER_PREMIUM_GUILD_SUBSCRIPTION,
        cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1,
        cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2,
        cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3,
        cls.CHANNEL_FOLLOW_ADD,
        cls.GUILD_DISCOVERY_DISQUALIFIED,
        cls.GUILD_DISCOVERY_REQUALIFIED,
        cls.GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING,
        cls.GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING,
        cls.THREAD_CREATED,
        cls.REPLY,
        cls.APPLICATION_COMMAND,
        cls.GUILD_INVITE_REMINDER,
        cls.CONTEXT_MENU_COMMAND,
        cls.AUTO_MODERATION_ACTION,
        cls.ROLE_SUBSCRIPTION_PURCHASE,
        cls.INTERACTION_PREMIUM_UPSELL,
        cls.STAGE_START,
        cls.STAGE_END,
        cls.STAGE_SPEAKER,
        cls.STAGE_TOPIC,
        cls.GUILD_APPLICATION_PREMIUM_SUBSCRIPTION,
        cls.GUILD_INCIDENT_ALERT_MODE_ENABLED,
        cls.GUILD_INCIDENT_ALERT_MODE_DISABLED,
        cls.GUILD_INCIDENT_REPORT_RAID,
        cls.GUILD_INCIDENT_REPORT_FALSE_ALARM,
        cls.PURCHASE_NOTIFICATION,
    )

NSFWLevel

Bases: CursedIntEnum

A guilds NSFW Level.

Source code in interactions/models/discord/enums.py
825
826
827
828
829
830
831
class NSFWLevel(CursedIntEnum):
    """A guilds NSFW Level."""

    DEFAULT = 0
    EXPLICIT = 1
    SAFE = 2
    AGE_RESTRICTED = 3

OnboardingMode

Bases: CursedIntEnum

Defines the criteria used to satisfy Onboarding constraints that are required for enabling.

Source code in interactions/models/discord/enums.py
765
766
767
768
769
770
771
class OnboardingMode(CursedIntEnum):
    """Defines the criteria used to satisfy Onboarding constraints that are required for enabling."""

    ONBOARDING_DEFAULT = 0
    """Counts only Default Channels towards constraints"""
    ONBOARDING_ADVANCED = 1
    """Counts Default Channels and Questions towards constraints"""

ONBOARDING_ADVANCED = 1 class-attribute

Counts Default Channels and Questions towards constraints

ONBOARDING_DEFAULT = 0 class-attribute

Counts only Default Channels towards constraints

OnboardingPromptType

Bases: CursedIntEnum

Types of Onboarding prompts.

Source code in interactions/models/discord/enums.py
774
775
776
777
778
class OnboardingPromptType(CursedIntEnum):
    """Types of Onboarding prompts."""

    MULTIPLE_CHOICE = 0
    DROPDOWN = 1

OverwriteType

Bases: CursedIntEnum

Types of permission overwrite.

Source code in interactions/models/discord/enums.py
781
782
783
784
785
class OverwriteType(CursedIntEnum):
    """Types of permission overwrite."""

    ROLE = 0
    MEMBER = 1

Permissions

Bases: DiscordIntFlag

Permissions a user or role may have.

Source code in interactions/models/discord/enums.py
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
class Permissions(DiscordIntFlag):  # type: ignore
    """Permissions a user or role may have."""

    # Permissions defined by Discord API
    CREATE_INSTANT_INVITE = 1 << 0
    """Allows creation of instant invites"""
    KICK_MEMBERS = 1 << 1
    """Allows kicking members"""
    BAN_MEMBERS = 1 << 2
    """Allows banning members"""
    ADMINISTRATOR = 1 << 3
    """Allows all permissions and bypasses channel permission overwrites"""
    MANAGE_CHANNELS = 1 << 4
    """Allows management and editing of channels"""
    MANAGE_GUILD = 1 << 5
    """Allows management and editing of the guild"""
    ADD_REACTIONS = 1 << 6
    """Allows for the addition of reactions to messages"""
    VIEW_AUDIT_LOG = 1 << 7
    """Allows for viewing of audit logs"""
    PRIORITY_SPEAKER = 1 << 8
    """Allows for using priority speaker in a voice channel"""
    STREAM = 1 << 9
    """Allows the user to go live"""
    VIEW_CHANNEL = 1 << 10
    """Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels"""
    SEND_MESSAGES = 1 << 11
    """	Allows for sending messages in a channel (does not allow sending messages in threads)"""
    CREATE_POSTS = 1 << 11
    """Allow members to create posts in this channel. Alias to SEND_MESSAGES"""
    SEND_TTS_MESSAGES = 1 << 12
    """	Allows for sending of `/tts` messages"""
    MANAGE_MESSAGES = 1 << 13
    """Allows for deletion of other users messages"""
    EMBED_LINKS = 1 << 14
    """Links sent by users with this permission will be auto-embedded"""
    ATTACH_FILES = 1 << 15
    """Allows for uploading images and files"""
    READ_MESSAGE_HISTORY = 1 << 16
    """Allows for reading of message history"""
    MENTION_EVERYONE = 1 << 17
    """Allows for using the `@everyone` tag to notify all users in a channel, and the `@here` tag to notify all online users in a channel"""
    USE_EXTERNAL_EMOJIS = 1 << 18
    """Allows the usage of custom emojis from other servers"""
    VIEW_GUILD_INSIGHTS = 1 << 19
    """Allows for viewing guild insights"""
    CONNECT = 1 << 20
    """Allows for joining of a voice channel"""
    SPEAK = 1 << 21
    """Allows for speaking in a voice channel"""
    MUTE_MEMBERS = 1 << 22
    """Allows for muting members in a voice channel"""
    DEAFEN_MEMBERS = 1 << 23
    """Allows for deafening of members in a voice channel"""
    MOVE_MEMBERS = 1 << 24
    """Allows for moving of members between voice channels"""
    USE_VAD = 1 << 25
    """Allows for using voice-activity-detection in a voice channel"""
    CHANGE_NICKNAME = 1 << 26
    """Allows for modification of own nickname"""
    MANAGE_NICKNAMES = 1 << 27
    """Allows for modification of other users nicknames"""
    MANAGE_ROLES = 1 << 28
    """Allows management and editing of roles"""
    MANAGE_WEBHOOKS = 1 << 29
    """Allows management and editing of webhooks"""
    MANAGE_EMOJIS_AND_STICKERS = 1 << 30
    """Allows management and editing of emojis and stickers"""
    USE_APPLICATION_COMMANDS = 1 << 31
    """Allows members to use application commands, including slash commands and context menu commands"""
    REQUEST_TO_SPEAK = 1 << 32
    """Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.)"""
    MANAGE_EVENTS = 1 << 33
    """Allows for creating, editing, and deleting scheduled events"""
    MANAGE_THREADS = 1 << 34
    """Allows for deleting and archiving threads, and viewing all private threads"""
    USE_PUBLIC_THREADS = 1 << 35
    """	Allows for creating public and announcement threads"""
    USE_PRIVATE_THREADS = 1 << 36
    """Allows for creating private threads"""
    USE_EXTERNAL_STICKERS = 1 << 37
    """Allows the usage of custom stickers from other servers"""
    SEND_MESSAGES_IN_THREADS = 1 << 38
    """Allows for sending messages in threads"""
    START_EMBEDDED_ACTIVITIES = 1 << 39
    """Allows for using Activities (applications with the `EMBEDDED` flag) in a voice channel"""
    MODERATE_MEMBERS = 1 << 40
    """Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels"""
    VIEW_CREATOR_MONETIZATION_ANALYTICS = 1 << 41
    """Allows for viewing guild monetization insights"""
    USE_SOUNDBOARD = 1 << 42
    """Allows for using the soundboard in a voice channel"""
    CREATE_GUILD_EXPRESSIONS = 1 << 43
    """Allows for creating emojis, stickers, and soundboard sounds"""
    USE_EXTERNAL_SOUNDS = 1 << 45
    """Allows the usage of custom sounds from other servers"""
    SEND_VOICE_MESSAGES = 1 << 46
    """Allows for sending audio messages"""
    SEND_POLLS = 1 << 49
    """Allows sending polls"""

    # Shortcuts/grouping/aliases
    REQUIRES_MFA = (
        KICK_MEMBERS
        | BAN_MEMBERS
        | ADMINISTRATOR
        | MANAGE_CHANNELS
        | MANAGE_GUILD
        | MANAGE_MESSAGES
        | MANAGE_ROLES
        | MANAGE_WEBHOOKS
        | MANAGE_EMOJIS_AND_STICKERS
        | MANAGE_THREADS
        | MODERATE_MEMBERS
    )
    USE_SLASH_COMMANDS = USE_APPLICATION_COMMANDS
    """Legacy alias for :attr:`USE_APPLICATION_COMMANDS`"""

    # Special members
    NONE = 0
    ALL = AntiFlag()

ADD_REACTIONS = 1 << 6 class-attribute

Allows for the addition of reactions to messages

ADMINISTRATOR = 1 << 3 class-attribute

Allows all permissions and bypasses channel permission overwrites

ATTACH_FILES = 1 << 15 class-attribute

Allows for uploading images and files

BAN_MEMBERS = 1 << 2 class-attribute

Allows banning members

CHANGE_NICKNAME = 1 << 26 class-attribute

Allows for modification of own nickname

CONNECT = 1 << 20 class-attribute

Allows for joining of a voice channel

CREATE_GUILD_EXPRESSIONS = 1 << 43 class-attribute

Allows for creating emojis, stickers, and soundboard sounds

CREATE_INSTANT_INVITE = 1 << 0 class-attribute

Allows creation of instant invites

CREATE_POSTS = 1 << 11 class-attribute

Allow members to create posts in this channel. Alias to SEND_MESSAGES

DEAFEN_MEMBERS = 1 << 23 class-attribute

Allows for deafening of members in a voice channel

Links sent by users with this permission will be auto-embedded

KICK_MEMBERS = 1 << 1 class-attribute

Allows kicking members

MANAGE_CHANNELS = 1 << 4 class-attribute

Allows management and editing of channels

MANAGE_EMOJIS_AND_STICKERS = 1 << 30 class-attribute

Allows management and editing of emojis and stickers

MANAGE_EVENTS = 1 << 33 class-attribute

Allows for creating, editing, and deleting scheduled events

MANAGE_GUILD = 1 << 5 class-attribute

Allows management and editing of the guild

MANAGE_MESSAGES = 1 << 13 class-attribute

Allows for deletion of other users messages

MANAGE_NICKNAMES = 1 << 27 class-attribute

Allows for modification of other users nicknames

MANAGE_ROLES = 1 << 28 class-attribute

Allows management and editing of roles

MANAGE_THREADS = 1 << 34 class-attribute

Allows for deleting and archiving threads, and viewing all private threads

MANAGE_WEBHOOKS = 1 << 29 class-attribute

Allows management and editing of webhooks

MENTION_EVERYONE = 1 << 17 class-attribute

Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all online users in a channel

MODERATE_MEMBERS = 1 << 40 class-attribute

Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels

MOVE_MEMBERS = 1 << 24 class-attribute

Allows for moving of members between voice channels

MUTE_MEMBERS = 1 << 22 class-attribute

Allows for muting members in a voice channel

PRIORITY_SPEAKER = 1 << 8 class-attribute

Allows for using priority speaker in a voice channel

READ_MESSAGE_HISTORY = 1 << 16 class-attribute

Allows for reading of message history

REQUEST_TO_SPEAK = 1 << 32 class-attribute

Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.)

SEND_MESSAGES = 1 << 11 class-attribute

Allows for sending messages in a channel (does not allow sending messages in threads)

SEND_MESSAGES_IN_THREADS = 1 << 38 class-attribute

Allows for sending messages in threads

SEND_POLLS = 1 << 49 class-attribute

Allows sending polls

SEND_TTS_MESSAGES = 1 << 12 class-attribute

Allows for sending of /tts messages

SEND_VOICE_MESSAGES = 1 << 46 class-attribute

Allows for sending audio messages

SPEAK = 1 << 21 class-attribute

Allows for speaking in a voice channel

START_EMBEDDED_ACTIVITIES = 1 << 39 class-attribute

Allows for using Activities (applications with the EMBEDDED flag) in a voice channel

STREAM = 1 << 9 class-attribute

Allows the user to go live

USE_APPLICATION_COMMANDS = 1 << 31 class-attribute

Allows members to use application commands, including slash commands and context menu commands

USE_EXTERNAL_EMOJIS = 1 << 18 class-attribute

Allows the usage of custom emojis from other servers

USE_EXTERNAL_SOUNDS = 1 << 45 class-attribute

Allows the usage of custom sounds from other servers

USE_EXTERNAL_STICKERS = 1 << 37 class-attribute

Allows the usage of custom stickers from other servers

USE_PRIVATE_THREADS = 1 << 36 class-attribute

Allows for creating private threads

USE_PUBLIC_THREADS = 1 << 35 class-attribute

Allows for creating public and announcement threads

USE_SLASH_COMMANDS = USE_APPLICATION_COMMANDS class-attribute

Legacy alias for :attr:USE_APPLICATION_COMMANDS

USE_SOUNDBOARD = 1 << 42 class-attribute

Allows for using the soundboard in a voice channel

USE_VAD = 1 << 25 class-attribute

Allows for using voice-activity-detection in a voice channel

VIEW_AUDIT_LOG = 1 << 7 class-attribute

Allows for viewing of audit logs

VIEW_CHANNEL = 1 << 10 class-attribute

Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels

VIEW_CREATOR_MONETIZATION_ANALYTICS = 1 << 41 class-attribute

Allows for viewing guild monetization insights

VIEW_GUILD_INSIGHTS = 1 << 19 class-attribute

Allows for viewing guild insights

PollLayoutType

Bases: CursedIntEnum

The layout of a poll.

Source code in interactions/models/discord/enums.py
1161
1162
1163
1164
class PollLayoutType(CursedIntEnum):
    """The layout of a poll."""

    DEFAULT = 1

PremiumTier

Bases: CursedIntEnum

The boost level of a server.

Source code in interactions/models/discord/enums.py
834
835
836
837
838
839
840
841
842
843
844
class PremiumTier(CursedIntEnum):
    """The boost level of a server."""

    NONE = 0
    """Guild has not unlocked any Server Boost perks"""
    TIER_1 = 1
    """Guild has unlocked Tier 1 Server Boost perks"""
    TIER_2 = 2
    """Guild has unlocked Tier 2 Server Boost perks"""
    TIER_3 = 3
    """Guild has unlocked Tier 3 Server Boost perks"""

NONE = 0 class-attribute

Guild has not unlocked any Server Boost perks

TIER_1 = 1 class-attribute

Guild has unlocked Tier 1 Server Boost perks

TIER_2 = 2 class-attribute

Guild has unlocked Tier 2 Server Boost perks

TIER_3 = 3 class-attribute

Guild has unlocked Tier 3 Server Boost perks

PremiumType

Bases: CursedIntEnum

Types of premium membership.

Source code in interactions/models/discord/enums.py
346
347
348
349
350
351
352
353
354
355
356
class PremiumType(CursedIntEnum):
    """Types of premium membership."""

    NONE = 0
    """No premium membership"""
    NITRO_CLASSIC = 1
    """Using Nitro Classic"""
    NITRO = 2
    """Full Nitro membership"""
    NITRO_BASIC = 3
    """Basic Nitro membership"""

NITRO = 2 class-attribute

Full Nitro membership

NITRO_BASIC = 3 class-attribute

Basic Nitro membership

NITRO_CLASSIC = 1 class-attribute

Using Nitro Classic

NONE = 0 class-attribute

No premium membership

ScheduledEventPrivacyLevel

Bases: CursedIntEnum

The privacy level of the scheduled event.

Source code in interactions/models/discord/enums.py
960
961
962
963
class ScheduledEventPrivacyLevel(CursedIntEnum):
    """The privacy level of the scheduled event."""

    GUILD_ONLY = 2

ScheduledEventStatus

Bases: CursedIntEnum

The status of the scheduled event.

Source code in interactions/models/discord/enums.py
977
978
979
980
981
982
983
class ScheduledEventStatus(CursedIntEnum):
    """The status of the scheduled event."""

    SCHEDULED = 1
    ACTIVE = 2
    COMPLETED = 3
    CANCELED = 4

ScheduledEventType

Bases: CursedIntEnum

The type of entity that the scheduled event is attached to.

Source code in interactions/models/discord/enums.py
966
967
968
969
970
971
972
973
974
class ScheduledEventType(CursedIntEnum):
    """The type of entity that the scheduled event is attached to."""

    STAGE_INSTANCE = 1
    """ Stage Channel """
    VOICE = 2
    """ Voice Channel """
    EXTERNAL = 3
    """ External URL """

EXTERNAL = 3 class-attribute

External URL

STAGE_INSTANCE = 1 class-attribute

Stage Channel

VOICE = 2 class-attribute

Voice Channel

Status

Bases: str, Enum

Represents the statuses a user may have.

Source code in interactions/models/discord/enums.py
932
933
934
935
936
937
938
939
940
941
942
class Status(str, Enum):
    """Represents the statuses a user may have."""

    ONLINE = "online"
    OFFLINE = "offline"
    DND = "dnd"
    IDLE = "idle"
    INVISIBLE = "invisible"

    AFK = IDLE
    DO_NOT_DISTURB = DND

StickerFormatType

Bases: CursedIntEnum

File formats for stickers.

Source code in interactions/models/discord/enums.py
1112
1113
1114
1115
1116
1117
1118
class StickerFormatType(CursedIntEnum):
    """File formats for stickers."""

    PNG = 1
    APNG = 2
    LOTTIE = 3
    GIF = 4

StickerTypes

Bases: CursedIntEnum

Types of sticker.

Source code in interactions/models/discord/enums.py
1103
1104
1105
1106
1107
1108
1109
class StickerTypes(CursedIntEnum):
    """Types of sticker."""

    STANDARD = 1
    """An official sticker in a pack, part of Nitro or in a removed purchasable pack."""
    GUILD = 2
    """A sticker uploaded to a Boosted guild for the guild's members."""

GUILD = 2 class-attribute

A sticker uploaded to a Boosted guild for the guild's members.

STANDARD = 1 class-attribute

An official sticker in a pack, part of Nitro or in a removed purchasable pack.

SystemChannelFlags

Bases: DiscordIntFlag

System channel settings.

Source code in interactions/models/discord/enums.py
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
class SystemChannelFlags(DiscordIntFlag):
    """System channel settings."""

    SUPPRESS_JOIN_NOTIFICATIONS = 1 << 0
    """Suppress member join notifications"""
    SUPPRESS_PREMIUM_SUBSCRIPTIONS = 1 << 1
    """Suppress server boost notifications"""
    SUPPRESS_GUILD_REMINDER_NOTIFICATIONS = 1 << 2
    """Suppress server setup tips"""
    SUPPRESS_JOIN_NOTIFICATION_REPLIES = 1 << 3
    """Hide member join sticker reply buttons"""

    # Special members
    NONE = 0
    ALL = AntiFlag()

SUPPRESS_GUILD_REMINDER_NOTIFICATIONS = 1 << 2 class-attribute

Suppress server setup tips

SUPPRESS_JOIN_NOTIFICATIONS = 1 << 0 class-attribute

Suppress member join notifications

SUPPRESS_JOIN_NOTIFICATION_REPLIES = 1 << 3 class-attribute

Hide member join sticker reply buttons

SUPPRESS_PREMIUM_SUBSCRIPTIONS = 1 << 1 class-attribute

Suppress server boost notifications

TeamMembershipState

Bases: CursedIntEnum

Status of membership in the team.

Source code in interactions/models/discord/enums.py
339
340
341
342
343
class TeamMembershipState(CursedIntEnum):
    """Status of membership in the team."""

    INVITED = 1
    ACCEPTED = 2

UserFlags

Bases: DiscordIntFlag

Flags a user can have.

Source code in interactions/models/discord/enums.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
class UserFlags(DiscordIntFlag):  # type: ignore
    """Flags a user can have."""

    DISCORD_EMPLOYEE = 1 << 0
    """This person works for Discord"""
    PARTNERED_SERVER_OWNER = 1 << 1
    """User owns a partnered server"""
    HYPESQUAD_EVENTS = 1 << 2
    """User has helped with a hypesquad event"""
    BUG_HUNTER_LEVEL_1 = 1 << 3
    """User has passed the bug hunters quiz"""

    HOUSE_BRAVERY = 1 << 6
    """User belongs to the `bravery` house"""
    HOUSE_BRILLIANCE = 1 << 7
    """User belongs to the `brilliance` house"""
    HOUSE_BALANCE = 1 << 8
    """User belongs to the `balance` house"""
    EARLY_SUPPORTER = 1 << 9
    """This person had Nitro prior to Wednesday, October 10th, 2018"""

    TEAM_USER = 1 << 10
    """A team user"""

    BUG_HUNTER_LEVEL_2 = 1 << 14
    """User is a bug hunter level 2"""

    VERIFIED_BOT = 1 << 16
    """This bot has been verified by Discord"""
    EARLY_VERIFIED_BOT_DEVELOPER = 1 << 17
    """This user was one of the first to be verified"""
    DISCORD_CERTIFIED_MODERATOR = 1 << 18
    """This user is a certified moderator"""

    BOT_HTTP_INTERACTIONS = 1 << 19
    """Bot uses only HTTP interactions and is shown in the online member list"""

    SPAMMER = 1 << 20
    """A user who is suspected of spamming"""
    DISABLE_PREMIUM = 1 << 21
    """Nitro features disabled for this user. Only used by Discord Staff for testing"""
    ACTIVE_DEVELOPER = 1 << 22
    """This user is an active developer"""

    # Shortcuts/grouping/aliases
    HYPESQUAD = HOUSE_BRAVERY | HOUSE_BRILLIANCE | HOUSE_BALANCE
    BUG_HUNTER = BUG_HUNTER_LEVEL_1 | BUG_HUNTER_LEVEL_2

    # Special members
    NONE = 0
    ALL = AntiFlag()

ACTIVE_DEVELOPER = 1 << 22 class-attribute

This user is an active developer

BOT_HTTP_INTERACTIONS = 1 << 19 class-attribute

Bot uses only HTTP interactions and is shown in the online member list

BUG_HUNTER_LEVEL_1 = 1 << 3 class-attribute

User has passed the bug hunters quiz

BUG_HUNTER_LEVEL_2 = 1 << 14 class-attribute

User is a bug hunter level 2

DISABLE_PREMIUM = 1 << 21 class-attribute

Nitro features disabled for this user. Only used by Discord Staff for testing

DISCORD_CERTIFIED_MODERATOR = 1 << 18 class-attribute

This user is a certified moderator

DISCORD_EMPLOYEE = 1 << 0 class-attribute

This person works for Discord

EARLY_SUPPORTER = 1 << 9 class-attribute

This person had Nitro prior to Wednesday, October 10th, 2018

EARLY_VERIFIED_BOT_DEVELOPER = 1 << 17 class-attribute

This user was one of the first to be verified

HOUSE_BALANCE = 1 << 8 class-attribute

User belongs to the balance house

HOUSE_BRAVERY = 1 << 6 class-attribute

User belongs to the bravery house

HOUSE_BRILLIANCE = 1 << 7 class-attribute

User belongs to the brilliance house

HYPESQUAD_EVENTS = 1 << 2 class-attribute

User has helped with a hypesquad event

PARTNERED_SERVER_OWNER = 1 << 1 class-attribute

User owns a partnered server

SPAMMER = 1 << 20 class-attribute

A user who is suspected of spamming

TEAM_USER = 1 << 10 class-attribute

A team user

VERIFIED_BOT = 1 << 16 class-attribute

This bot has been verified by Discord

VerificationLevel

Bases: CursedIntEnum

Levels of verification needed by a guild.

Source code in interactions/models/discord/enums.py
810
811
812
813
814
815
816
817
818
819
820
821
822
class VerificationLevel(CursedIntEnum):
    """Levels of verification needed by a guild."""

    NONE = 0
    """No verification needed"""
    LOW = 1
    """Must have a verified email on their Discord Account"""
    MEDIUM = 2
    """Must also be registered on Discord for longer than 5 minutes"""
    HIGH = 3
    """Must also be a member of this server for longer than 10 minutes"""
    VERY_HIGH = 4
    """Must have a verified phone number on their Discord Account"""

HIGH = 3 class-attribute

Must also be a member of this server for longer than 10 minutes

LOW = 1 class-attribute

Must have a verified email on their Discord Account

MEDIUM = 2 class-attribute

Must also be registered on Discord for longer than 5 minutes

NONE = 0 class-attribute

No verification needed

VERY_HIGH = 4 class-attribute

Must have a verified phone number on their Discord Account

VideoQualityMode

Bases: CursedIntEnum

Video quality settings.

Source code in interactions/models/discord/enums.py
878
879
880
881
882
class VideoQualityMode(CursedIntEnum):
    """Video quality settings."""

    AUTO = 1
    FULL = 2

WebSocketOPCode

Bases: CursedIntEnum

Codes used by the Gateway to signify events.

Source code in interactions/models/discord/enums.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
class WebSocketOPCode(CursedIntEnum):
    """Codes used by the Gateway to signify events."""

    DISPATCH = 0
    """An event was dispatched"""
    HEARTBEAT = 1
    """Fired periodically by the client to keep the connection alive"""
    IDENTIFY = 2
    """Starts a new session during the initial handshake."""
    PRESENCE = 3
    """Update the client's presence."""
    VOICE_STATE = 4
    """Used to join/leave or move between voice channels."""
    VOICE_PING = 5
    RESUME = 6
    """Resume a previous session that was disconnected."""
    RECONNECT = 7
    """You should attempt to reconnect and resume immediately."""
    REQUEST_MEMBERS = 8
    """Request information about offline guild members in a large guild."""
    INVALIDATE_SESSION = 9
    """The session has been invalidated. You should reconnect and identify/resume accordingly."""
    HELLO = 10
    """Sent immediately after connecting, contains the `heartbeat_interval` to use."""
    HEARTBEAT_ACK = 11
    """Sent in response to receiving a heartbeat to acknowledge that it has been received."""
    GUILD_SYNC = 12

DISPATCH = 0 class-attribute

An event was dispatched

HEARTBEAT = 1 class-attribute

Fired periodically by the client to keep the connection alive

HEARTBEAT_ACK = 11 class-attribute

Sent in response to receiving a heartbeat to acknowledge that it has been received.

HELLO = 10 class-attribute

Sent immediately after connecting, contains the heartbeat_interval to use.

IDENTIFY = 2 class-attribute

Starts a new session during the initial handshake.

INVALIDATE_SESSION = 9 class-attribute

The session has been invalidated. You should reconnect and identify/resume accordingly.

PRESENCE = 3 class-attribute

Update the client's presence.

RECONNECT = 7 class-attribute

You should attempt to reconnect and resume immediately.

REQUEST_MEMBERS = 8 class-attribute

Request information about offline guild members in a large guild.

RESUME = 6 class-attribute

Resume a previous session that was disconnected.

VOICE_STATE = 4 class-attribute

Used to join/leave or move between voice channels.

Timestamp

Bases: datetime

A special class that represents Discord timestamps.

Assumes that all naive datetimes are based on local timezone.

Source code in interactions/models/discord/timestamp.py
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
class Timestamp(datetime):
    """
    A special class that represents Discord timestamps.

    Assumes that all naive datetimes are based on local timezone.

    """

    @classmethod
    def fromdatetime(cls, dt: datetime) -> "Timestamp":
        """Construct a timezone-aware UTC datetime from a datetime object."""
        timestamp = cls.fromtimestamp(dt.timestamp(), tz=dt.tzinfo)

        return timestamp.astimezone() if timestamp.tzinfo is None else timestamp

    @classmethod
    def utcfromtimestamp(cls, t: float) -> "Timestamp":
        """Construct a timezone-aware UTC datetime from a POSIX timestamp."""
        return super().utcfromtimestamp(t).replace(tzinfo=timezone.utc)

    @classmethod
    def fromisoformat(cls, date_string: str) -> "Timestamp":
        timestamp = super().fromisoformat(date_string)

        return timestamp.astimezone() if timestamp.tzinfo is None else timestamp

    @classmethod
    def fromisocalendar(cls, year: int, week: int, day: int) -> "Timestamp":
        return super().fromisocalendar(year, week, day).astimezone()

    @classmethod
    def fromtimestamp(cls, t: float, tz=None) -> "Timestamp":
        if sys.platform == "win32" and t < 0:
            raise ValueError("Negative timestamps are not supported on Windows.")

        try:
            timestamp = super().fromtimestamp(t, tz=tz)
        except Exception:
            # May be in milliseconds instead of seconds
            timestamp = super().fromtimestamp(t / 1000, tz=tz)

        return timestamp.astimezone() if timestamp.tzinfo is None else timestamp

    @classmethod
    def fromordinal(cls, n: int) -> "Timestamp":
        return super().fromordinal(n).astimezone()

    @classmethod
    def now(cls, tz=None) -> "Timestamp":
        """
        Construct a datetime from time.time() and optional time zone info.

        If no timezone is provided, the time is assumed to be from the computer's
        local timezone.
        """
        t = time.time()
        return cls.fromtimestamp(t, tz)

    @classmethod
    def utcnow(cls) -> "Timestamp":
        """Construct a timezone-aware UTC datetime from time.time()."""
        t = time.time()
        return cls.utcfromtimestamp(t)

    def astimezone(self, tz: tzinfo | None = None) -> "Timestamp":
        # workaround of https://github.com/python/cpython/issues/107078

        if sys.platform != "win32":
            return super().astimezone(tz)

        # this bound is loose, but it's good enough for our purposes
        if self.year > 1970 or (self.year == 1970 and (self.month > 1 or self.day > 1)):
            return super().astimezone(tz)

        if self.year < 1969 or (self.year == 1969 and (self.month < 12 or self.day < 31)):
            # windows kind of breaks down for dates before unix time
            # technically this is solvable, but it's not worth the effort
            # also, again, this is a loose bound, but it's good enough for our purposes
            raise ValueError("astimezone with no arguments is not supported for dates before Unix Time on Windows.")

        if tz:
            return self.replace(tzinfo=tz)

        # to work around the issue to some extent, we'll use a timestamp with a date
        # that doesn't trigger the bug, and use the timezone from it to modify this
        # timestamp
        sample_datetime = Timestamp(1970, 1, 5).astimezone()
        return self.replace(tzinfo=sample_datetime.tzinfo)

    def to_snowflake(self, high: bool = False) -> Union[str, "Snowflake"]:
        """
        Returns a numeric snowflake pretending to be created at the given date.

        When using as the lower end of a range, use ``tosnowflake(high=False) - 1``
        to be inclusive, ``high=True`` to be exclusive.
        When using as the higher end of a range, use ``tosnowflake(high=True) + 1``
        to be inclusive, ``high=False`` to be exclusive

        """
        discord_millis = int(self.timestamp() * 1000 - DISCORD_EPOCH)
        return (discord_millis << 22) + (2**22 - 1 if high else 0)

    @classmethod
    def from_snowflake(cls, snowflake: "Snowflake_Type") -> "Timestamp":
        """
        Construct a timezone-aware UTC datetime from a snowflake.

        Args:
            snowflake: The snowflake to convert.

        Returns:
            A timezone-aware UTC datetime.

        ??? Info
            https://discord.com/developers/docs/reference#convert-snowflake-to-datetime

        """
        if isinstance(snowflake, str):
            snowflake = int(snowflake)

        timestamp = ((snowflake >> 22) + DISCORD_EPOCH) / 1000
        return cls.utcfromtimestamp(timestamp)

    def format(self, style: Optional[Union[TimestampStyles, str]] = None) -> str:
        """
        Format the timestamp for discord client to display.

        Args:
            style: The style to format the timestamp with.

        Returns:
            The formatted timestamp.

        """
        return f"<t:{self.timestamp():.0f}:{style}>" if style else f"<t:{self.timestamp():.0f}>"

    def __str__(self) -> str:
        return self.format()

format(style=None)

Format the timestamp for discord client to display.

Parameters:

Name Type Description Default
style Optional[Union[TimestampStyles, str]]

The style to format the timestamp with.

None

Returns:

Type Description
str

The formatted timestamp.

Source code in interactions/models/discord/timestamp.py
151
152
153
154
155
156
157
158
159
160
161
162
def format(self, style: Optional[Union[TimestampStyles, str]] = None) -> str:
    """
    Format the timestamp for discord client to display.

    Args:
        style: The style to format the timestamp with.

    Returns:
        The formatted timestamp.

    """
    return f"<t:{self.timestamp():.0f}:{style}>" if style else f"<t:{self.timestamp():.0f}>"

from_snowflake(snowflake) classmethod

Construct a timezone-aware UTC datetime from a snowflake.

Parameters:

Name Type Description Default
snowflake Snowflake_Type

The snowflake to convert.

required

Returns:

Type Description
Timestamp

A timezone-aware UTC datetime.

Info

https://discord.com/developers/docs/reference#convert-snowflake-to-datetime

Source code in interactions/models/discord/timestamp.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
@classmethod
def from_snowflake(cls, snowflake: "Snowflake_Type") -> "Timestamp":
    """
    Construct a timezone-aware UTC datetime from a snowflake.

    Args:
        snowflake: The snowflake to convert.

    Returns:
        A timezone-aware UTC datetime.

    ??? Info
        https://discord.com/developers/docs/reference#convert-snowflake-to-datetime

    """
    if isinstance(snowflake, str):
        snowflake = int(snowflake)

    timestamp = ((snowflake >> 22) + DISCORD_EPOCH) / 1000
    return cls.utcfromtimestamp(timestamp)

fromdatetime(dt) classmethod

Construct a timezone-aware UTC datetime from a datetime object.

Source code in interactions/models/discord/timestamp.py
36
37
38
39
40
41
@classmethod
def fromdatetime(cls, dt: datetime) -> "Timestamp":
    """Construct a timezone-aware UTC datetime from a datetime object."""
    timestamp = cls.fromtimestamp(dt.timestamp(), tz=dt.tzinfo)

    return timestamp.astimezone() if timestamp.tzinfo is None else timestamp

now(tz=None) classmethod

Construct a datetime from time.time() and optional time zone info.

If no timezone is provided, the time is assumed to be from the computer's local timezone.

Source code in interactions/models/discord/timestamp.py
75
76
77
78
79
80
81
82
83
84
@classmethod
def now(cls, tz=None) -> "Timestamp":
    """
    Construct a datetime from time.time() and optional time zone info.

    If no timezone is provided, the time is assumed to be from the computer's
    local timezone.
    """
    t = time.time()
    return cls.fromtimestamp(t, tz)

to_snowflake(high=False)

Returns a numeric snowflake pretending to be created at the given date.

When using as the lower end of a range, use tosnowflake(high=False) - 1 to be inclusive, high=True to be exclusive. When using as the higher end of a range, use tosnowflake(high=True) + 1 to be inclusive, high=False to be exclusive

Source code in interactions/models/discord/timestamp.py
117
118
119
120
121
122
123
124
125
126
127
128
def to_snowflake(self, high: bool = False) -> Union[str, "Snowflake"]:
    """
    Returns a numeric snowflake pretending to be created at the given date.

    When using as the lower end of a range, use ``tosnowflake(high=False) - 1``
    to be inclusive, ``high=True`` to be exclusive.
    When using as the higher end of a range, use ``tosnowflake(high=True) + 1``
    to be inclusive, ``high=False`` to be exclusive

    """
    discord_millis = int(self.timestamp() * 1000 - DISCORD_EPOCH)
    return (discord_millis << 22) + (2**22 - 1 if high else 0)

utcfromtimestamp(t) classmethod

Construct a timezone-aware UTC datetime from a POSIX timestamp.

Source code in interactions/models/discord/timestamp.py
43
44
45
46
@classmethod
def utcfromtimestamp(cls, t: float) -> "Timestamp":
    """Construct a timezone-aware UTC datetime from a POSIX timestamp."""
    return super().utcfromtimestamp(t).replace(tzinfo=timezone.utc)

utcnow() classmethod

Construct a timezone-aware UTC datetime from time.time().

Source code in interactions/models/discord/timestamp.py
86
87
88
89
90
@classmethod
def utcnow(cls) -> "Timestamp":
    """Construct a timezone-aware UTC datetime from time.time()."""
    t = time.time()
    return cls.utcfromtimestamp(t)

Internal Models

Extension

A class that allows you to separate your commands and listeners into separate files. Extensions require an entrypoint in the same file called setup, this function allows client to load the Extension.

Example Usage:
1
2
3
4
5
6
7
class ExampleExt(Extension):
    def __init__(self, bot):
        print("Extension Created")

    @prefixed_command()
    async def some_command(self, context):
        await ctx.send(f"I was sent from a extension called {self.name}")

Attributes:

Name Type Description
bot Client

A reference to the client

name str

The name of this Extension (read-only)

description str

A description of this Extension

extension_checks str

A list of checks to be ran on any command in this extension

extension_prerun List

A list of coroutines to be run before any command in this extension

extension_postrun List

A list of coroutines to be run after any command in this extension

interaction_tree Dict

A dictionary of registered application commands in a tree

Source code in interactions/models/internal/extension.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
class Extension:
    """
    A class that allows you to separate your commands and listeners into separate files. Extensions require an entrypoint in the same file called `setup`, this function allows client to load the Extension.

    ??? Hint "Example Usage:"
        ```python
        class ExampleExt(Extension):
            def __init__(self, bot):
                print("Extension Created")

            @prefixed_command()
            async def some_command(self, context):
                await ctx.send(f"I was sent from a extension called {self.name}")
        ```

    Attributes:
        bot Client: A reference to the client
        name str: The name of this Extension (`read-only`)
        description str: A description of this Extension
        extension_checks str: A list of checks to be ran on any command in this extension
        extension_prerun List: A list of coroutines to be run before any command in this extension
        extension_postrun List: A list of coroutines to be run after any command in this extension
        interaction_tree Dict: A dictionary of registered application commands in a tree

    """

    bot: "Client"
    name: str
    extension_name: str
    description: str
    extension_checks: List
    extension_prerun: List
    extension_postrun: List
    extension_error: Optional[Callable[..., Coroutine]]
    interaction_tree: Dict["Snowflake_Type", Dict[str, "InteractionCommand" | Dict[str, "InteractionCommand"]]]
    _commands: List
    _listeners: List
    auto_defer: "AutoDefer"

    class Metadata:
        """Info about the Extension"""

        name: str
        """The name of this Extension"""
        version: str
        """The version of this Extension"""
        url: str
        """The repository url of this Extension"""
        description: str
        """A description of this Extension"""
        requirements: List[str]
        """A list of requirements for this Extension"""

    def __new__(cls, bot: "Client", *args, **kwargs) -> "Extension":
        instance = super().__new__(cls)
        instance.bot = bot
        instance.client = bot

        instance.name = cls.__name__

        if instance.name in bot.ext:
            raise ValueError(f"An extension with the name {instance.name} is already loaded!")

        instance.extension_name = inspect.getmodule(instance).__name__
        instance.extension_checks = []
        instance.extension_prerun = []
        instance.extension_postrun = []
        instance.extension_error = None
        instance.interaction_tree = {}
        instance.auto_defer = MISSING

        instance.description = kwargs.get("Description")
        if not instance.description:
            instance.description = inspect.cleandoc(cls.__doc__) if cls.__doc__ else None

        # load commands from class
        instance._commands = []
        instance._listeners = []

        callables: list[tuple[str, typing.Callable]] = inspect.getmembers(
            instance, predicate=lambda x: isinstance(x, (CallbackObject, Task))
        )

        for _name, val in callables:
            if isinstance(val, models.BaseCommand):
                val.extension = instance
                val = wrap_partial(val, instance)
                bot.add_command(val)
                instance._commands.append(val)

            elif isinstance(val, Task):
                wrap_partial(val, instance)

            elif isinstance(val, models.Listener):
                val.extension = instance
                val = val.copy_with_binding(instance)
                bot.add_listener(val)  # type: ignore
                instance._listeners.append(val)
            elif isinstance(val, models.GlobalAutoComplete):
                val.extension = instance
                val = val.copy_with_binding(instance)
                bot.add_global_autocomplete(val)
        bot.dispatch(events.ExtensionCommandParse(extension=instance, callables=callables))

        instance.bot.ext[instance.name] = instance

        if hasattr(instance, "async_start"):
            if inspect.iscoroutinefunction(instance.async_start):
                bot.async_startup_tasks.append((instance.async_start, (), {}))
            else:
                raise TypeError("async_start is a reserved method and must be a coroutine")

        bot.dispatch(events.ExtensionLoad(extension=instance))
        return instance

    @property
    def __name__(self) -> str:
        return self.name

    @property
    def commands(self) -> List["BaseCommand"]:
        """Get the commands from this Extension."""
        return self._commands

    @property
    def listeners(self) -> List["Listener"]:
        """Get the listeners from this Extension."""
        return self._listeners

    def drop(self) -> None:
        """Called when this Extension is being removed."""
        for func in self._commands:
            if isinstance(func, models.ModalCommand):
                for listener in func.listeners:
                    if isinstance(listener, re.Pattern):
                        # noinspection PyProtectedMember
                        self.bot._regex_modal_callbacks.pop(listener)
                    else:
                        # noinspection PyProtectedMember
                        self.bot._modal_callbacks.pop(listener)
            elif isinstance(func, models.ComponentCommand):
                for listener in func.listeners:
                    if isinstance(listener, re.Pattern):
                        # noinspection PyProtectedMember
                        self.bot._regex_component_callbacks.pop(listener)
                    else:
                        # noinspection PyProtectedMember
                        self.bot._component_callbacks.pop(listener)
            elif isinstance(func, models.InteractionCommand):
                for scope in func.scopes:
                    if self.bot.interactions_by_scope.get(scope):
                        self.bot.interactions_by_scope[scope].pop(func.resolved_name, [])
        for func in self.listeners:
            self.bot.listeners[func.event].remove(func)

        self.bot.ext.pop(self.name, None)
        self.bot.dispatch(events.ExtensionUnload(extension=self))
        self.bot.logger.debug(f"{self.name} has been drop")

    def add_ext_auto_defer(self, enabled: bool = True, ephemeral: bool = False, time_until_defer: float = 0.0) -> None:
        """
        Add a auto defer for all commands in this extension.

        Args:
            enabled: Should the command be deferred automatically
            ephemeral: Should the command be deferred as ephemeral
            time_until_defer: How long to wait before deferring automatically

        """
        self.auto_defer = models.AutoDefer(enabled=enabled, ephemeral=ephemeral, time_until_defer=time_until_defer)

    def add_ext_check(self, coroutine: Callable[["BaseContext"], Awaitable[bool]]) -> None:
        """
        Add a coroutine as a check for all commands in this extension to run. This coroutine must take **only** the parameter `context`.

        ??? Hint "Example Usage:"
            ```python
            def __init__(self, bot):
                self.add_ext_check(self.example)

            @staticmethod
            async def example(context: BaseContext):
                if context.author.id == 123456789:
                    return True
                return False
            ```
        Args:
            coroutine: The coroutine to use as a check

        """
        if not asyncio.iscoroutinefunction(coroutine):
            raise TypeError("Check must be a coroutine")

        if not self.extension_checks:
            self.extension_checks = []

        self.extension_checks.append(coroutine)

    def add_extension_prerun(self, coroutine: Callable[..., Coroutine]) -> None:
        """
        Add a coroutine to be run **before** all commands in this Extension.

        !!! note
            Pre-runs will **only** be run if the commands checks pass

        ??? Hint "Example Usage:"
            ```python
            def __init__(self, bot):
                self.add_extension_prerun(self.example)

            async def example(self, context: BaseContext):
                await ctx.send("I ran first")
            ```

        Args:
            coroutine: The coroutine to run

        """
        if not asyncio.iscoroutinefunction(coroutine):
            raise TypeError("Callback must be a coroutine")

        if not self.extension_prerun:
            self.extension_prerun = []
        self.extension_prerun.append(coroutine)

    def add_extension_postrun(self, coroutine: Callable[..., Coroutine]) -> None:
        """
        Add a coroutine to be run **after** all commands in this Extension.

        ??? Hint "Example Usage:"
            ```python
            def __init__(self, bot):
                self.add_extension_postrun(self.example)

            async def example(self, context: BaseContext):
                await ctx.send("I ran first")
            ```

        Args:
            coroutine: The coroutine to run

        """
        if not asyncio.iscoroutinefunction(coroutine):
            raise TypeError("Callback must be a coroutine")

        if not self.extension_postrun:
            self.extension_postrun = []
        self.extension_postrun.append(coroutine)

    def set_extension_error(self, coroutine: Callable[..., Coroutine]) -> None:
        """
        Add a coroutine to handle any exceptions raised in this extension.

        ??? Hint "Example Usage:"
            ```python
            def __init__(self, bot):
                self.set_extension_error(self.example)

        Args:
            coroutine: The coroutine to run

        """
        if not asyncio.iscoroutinefunction(coroutine):
            raise TypeError("Callback must be a coroutine")

        if self.extension_error:
            self.bot.logger.warning("Extension error callback has been overridden!")
        self.extension_error = coroutine

commands: List[BaseCommand] property

Get the commands from this Extension.

listeners: List[Listener] property

Get the listeners from this Extension.

Metadata

Info about the Extension

Source code in interactions/models/internal/extension.py
63
64
65
66
67
68
69
70
71
72
73
74
75
class Metadata:
    """Info about the Extension"""

    name: str
    """The name of this Extension"""
    version: str
    """The version of this Extension"""
    url: str
    """The repository url of this Extension"""
    description: str
    """A description of this Extension"""
    requirements: List[str]
    """A list of requirements for this Extension"""

description: str class-attribute

A description of this Extension

name: str class-attribute

The name of this Extension

requirements: List[str] class-attribute

A list of requirements for this Extension

url: str class-attribute

The repository url of this Extension

version: str class-attribute

The version of this Extension

add_ext_auto_defer(enabled=True, ephemeral=False, time_until_defer=0.0)

Add a auto defer for all commands in this extension.

Parameters:

Name Type Description Default
enabled bool

Should the command be deferred automatically

True
ephemeral bool

Should the command be deferred as ephemeral

False
time_until_defer float

How long to wait before deferring automatically

0.0
Source code in interactions/models/internal/extension.py
183
184
185
186
187
188
189
190
191
192
193
def add_ext_auto_defer(self, enabled: bool = True, ephemeral: bool = False, time_until_defer: float = 0.0) -> None:
    """
    Add a auto defer for all commands in this extension.

    Args:
        enabled: Should the command be deferred automatically
        ephemeral: Should the command be deferred as ephemeral
        time_until_defer: How long to wait before deferring automatically

    """
    self.auto_defer = models.AutoDefer(enabled=enabled, ephemeral=ephemeral, time_until_defer=time_until_defer)

add_ext_check(coroutine)

Add a coroutine as a check for all commands in this extension to run. This coroutine must take only the parameter context.

Example Usage:
1
2
3
4
5
6
7
8
def __init__(self, bot):
    self.add_ext_check(self.example)

@staticmethod
async def example(context: BaseContext):
    if context.author.id == 123456789:
        return True
    return False

Parameters:

Name Type Description Default
coroutine Callable[[BaseContext], Awaitable[bool]]

The coroutine to use as a check

required
Source code in interactions/models/internal/extension.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
def add_ext_check(self, coroutine: Callable[["BaseContext"], Awaitable[bool]]) -> None:
    """
    Add a coroutine as a check for all commands in this extension to run. This coroutine must take **only** the parameter `context`.

    ??? Hint "Example Usage:"
        ```python
        def __init__(self, bot):
            self.add_ext_check(self.example)

        @staticmethod
        async def example(context: BaseContext):
            if context.author.id == 123456789:
                return True
            return False
        ```
    Args:
        coroutine: The coroutine to use as a check

    """
    if not asyncio.iscoroutinefunction(coroutine):
        raise TypeError("Check must be a coroutine")

    if not self.extension_checks:
        self.extension_checks = []

    self.extension_checks.append(coroutine)

add_extension_postrun(coroutine)

Add a coroutine to be run after all commands in this Extension.

Example Usage:
1
2
3
4
5
def __init__(self, bot):
    self.add_extension_postrun(self.example)

async def example(self, context: BaseContext):
    await ctx.send("I ran first")

Parameters:

Name Type Description Default
coroutine Callable[..., Coroutine]

The coroutine to run

required
Source code in interactions/models/internal/extension.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
def add_extension_postrun(self, coroutine: Callable[..., Coroutine]) -> None:
    """
    Add a coroutine to be run **after** all commands in this Extension.

    ??? Hint "Example Usage:"
        ```python
        def __init__(self, bot):
            self.add_extension_postrun(self.example)

        async def example(self, context: BaseContext):
            await ctx.send("I ran first")
        ```

    Args:
        coroutine: The coroutine to run

    """
    if not asyncio.iscoroutinefunction(coroutine):
        raise TypeError("Callback must be a coroutine")

    if not self.extension_postrun:
        self.extension_postrun = []
    self.extension_postrun.append(coroutine)

add_extension_prerun(coroutine)

Add a coroutine to be run before all commands in this Extension.

Note

Pre-runs will only be run if the commands checks pass

Example Usage:
1
2
3
4
5
def __init__(self, bot):
    self.add_extension_prerun(self.example)

async def example(self, context: BaseContext):
    await ctx.send("I ran first")

Parameters:

Name Type Description Default
coroutine Callable[..., Coroutine]

The coroutine to run

required
Source code in interactions/models/internal/extension.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
def add_extension_prerun(self, coroutine: Callable[..., Coroutine]) -> None:
    """
    Add a coroutine to be run **before** all commands in this Extension.

    !!! note
        Pre-runs will **only** be run if the commands checks pass

    ??? Hint "Example Usage:"
        ```python
        def __init__(self, bot):
            self.add_extension_prerun(self.example)

        async def example(self, context: BaseContext):
            await ctx.send("I ran first")
        ```

    Args:
        coroutine: The coroutine to run

    """
    if not asyncio.iscoroutinefunction(coroutine):
        raise TypeError("Callback must be a coroutine")

    if not self.extension_prerun:
        self.extension_prerun = []
    self.extension_prerun.append(coroutine)

drop()

Called when this Extension is being removed.

Source code in interactions/models/internal/extension.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def drop(self) -> None:
    """Called when this Extension is being removed."""
    for func in self._commands:
        if isinstance(func, models.ModalCommand):
            for listener in func.listeners:
                if isinstance(listener, re.Pattern):
                    # noinspection PyProtectedMember
                    self.bot._regex_modal_callbacks.pop(listener)
                else:
                    # noinspection PyProtectedMember
                    self.bot._modal_callbacks.pop(listener)
        elif isinstance(func, models.ComponentCommand):
            for listener in func.listeners:
                if isinstance(listener, re.Pattern):
                    # noinspection PyProtectedMember
                    self.bot._regex_component_callbacks.pop(listener)
                else:
                    # noinspection PyProtectedMember
                    self.bot._component_callbacks.pop(listener)
        elif isinstance(func, models.InteractionCommand):
            for scope in func.scopes:
                if self.bot.interactions_by_scope.get(scope):
                    self.bot.interactions_by_scope[scope].pop(func.resolved_name, [])
    for func in self.listeners:
        self.bot.listeners[func.event].remove(func)

    self.bot.ext.pop(self.name, None)
    self.bot.dispatch(events.ExtensionUnload(extension=self))
    self.bot.logger.debug(f"{self.name} has been drop")

set_extension_error(coroutine)

Add a coroutine to handle any exceptions raised in this extension.

Example Usage:

```python def init(self, bot): self.set_extension_error(self.example)

Args: coroutine: The coroutine to run

Source code in interactions/models/internal/extension.py
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
def set_extension_error(self, coroutine: Callable[..., Coroutine]) -> None:
    """
    Add a coroutine to handle any exceptions raised in this extension.

    ??? Hint "Example Usage:"
        ```python
        def __init__(self, bot):
            self.set_extension_error(self.example)

    Args:
        coroutine: The coroutine to run

    """
    if not asyncio.iscoroutinefunction(coroutine):
        raise TypeError("Callback must be a coroutine")

    if self.extension_error:
        self.bot.logger.warning("Extension error callback has been overridden!")
    self.extension_error = coroutine

Buckets

Bases: IntEnum

Outlines the cooldown buckets that may be used. Should a bucket for guilds exist, and the command is invoked in a DM, a sane default will be used.

Note

To add your own, override this

Source code in interactions/models/internal/cooldowns.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class Buckets(IntEnum):
    """
    Outlines the cooldown buckets that may be used. Should a bucket for guilds exist, and the command is invoked in a DM, a sane default will be used.

    ??? note
         To add your own, override this

    """

    DEFAULT = 0
    """Default is the same as user"""
    USER = 1
    """Per user cooldowns"""
    GUILD = 2
    """Per guild cooldowns"""
    CHANNEL = 3
    """Per channel cooldowns"""
    MEMBER = 4
    """Per guild member cooldowns"""
    CATEGORY = 5
    """Per category cooldowns"""
    ROLE = 6
    """Per role cooldowns"""

    async def get_key(self, context: "BaseContext") -> Any:
        if self is Buckets.USER:
            return context.author.id
        if self is Buckets.GUILD:
            return context.guild_id or context.author.id
        if self is Buckets.CHANNEL:
            return context.channel.id
        if self is Buckets.MEMBER:
            return (context.guild_id, context.author.id) if context.guild_id else context.author.id
        if self is Buckets.CATEGORY:
            return await context.channel.parent_id if context.channel.parent else context.channel.id
        if self is Buckets.ROLE:
            return context.author.top_role.id if context.guild_id else context.channel.id
        return context.author.id

    def __call__(self, context: "BaseContext") -> Any:
        return self.get_key(context)

CATEGORY = 5 class-attribute

Per category cooldowns

CHANNEL = 3 class-attribute

Per channel cooldowns

DEFAULT = 0 class-attribute

Default is the same as user

GUILD = 2 class-attribute

Per guild cooldowns

MEMBER = 4 class-attribute

Per guild member cooldowns

ROLE = 6 class-attribute

Per role cooldowns

USER = 1 class-attribute

Per user cooldowns

Cooldown

Manages cooldowns and their respective buckets for a command.

There are two pre-defined cooldown systems, a sliding window and a standard cooldown system (default); you can specify which one to use by passing in the cooldown_system parameter.

Attributes:

Name Type Description
bucket Buckets

The bucket to use for this cooldown

cooldown_repositories

A dictionary of cooldowns for each bucket

rate int

How many commands may be ran per interval

interval float

How many seconds to wait for a cooldown

cooldown_system Type[CooldownSystem]

The cooldown system to use for this cooldown

Source code in interactions/models/internal/cooldowns.py
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
class Cooldown:
    """
    Manages cooldowns and their respective buckets for a command.

    There are two pre-defined cooldown systems, a sliding window and a standard cooldown system (default);
    you can specify which one to use by passing in the cooldown_system parameter.

    Attributes:
        bucket: The bucket to use for this cooldown
        cooldown_repositories: A dictionary of cooldowns for each bucket
        rate: How many commands may be ran per interval
        interval: How many seconds to wait for a cooldown
        cooldown_system: The cooldown system to use for this cooldown

    """

    __slots__ = "bucket", "cooldown_repositories", "rate", "interval", "cooldown_system"

    def __init__(
        self,
        cooldown_bucket: Buckets,
        rate: int,
        interval: float,
        *,
        cooldown_system: Type[CooldownSystem] = CooldownSystem,
    ) -> None:
        self.bucket: Buckets = cooldown_bucket
        self.cooldown_repositories = {}
        self.rate: int = rate
        self.interval: float = interval

        self.cooldown_system: Type[CooldownSystem] = cooldown_system or CooldownSystem

    async def get_cooldown(self, context: "BaseContext") -> "CooldownSystem":
        key = await self.bucket(context)

        if key not in self.cooldown_repositories:
            cooldown = self.cooldown_system(self.rate, self.interval)
            self.cooldown_repositories[key] = cooldown
            return cooldown
        return self.cooldown_repositories.get(await self.bucket(context))

    def get_cooldown_with_key(self, key: Any, *, create: bool = False) -> typing.Optional["CooldownSystem"]:
        """
        Get the cooldown system for the command.

        Note:
            The preferred way to get the cooldown system is to use `get_cooldown` as it will use the context to get the correct key.

        Args:
            key: The key to get the cooldown system for
            create: Whether to create a new cooldown system if one does not exist

        """
        if key not in self.cooldown_repositories and create:
            cooldown = self.cooldown_system(self.rate, self.interval)
            self.cooldown_repositories[key] = cooldown
            return cooldown
        return self.cooldown_repositories.get(key)

    async def acquire_token(self, context: "BaseContext") -> bool:
        """
        Attempt to acquire a token for a command to run. Uses the context of the command to use the correct CooldownStrategy.

        Args:
            context: The context of the command

        Returns:
            True if a token was acquired, False if not

        """
        cooldown = await self.get_cooldown(context)

        return cooldown.acquire_token()

    async def get_cooldown_time(self, context: "BaseContext") -> float:
        """
        Get the remaining cooldown time.

        Args:
            context: The context of the command

        Returns:
            remaining cooldown time, will return 0 if the cooldown has not been reached

        """
        cooldown = await self.get_cooldown(context)
        return cooldown.get_cooldown_time()

    def get_cooldown_time_with_key(self, key: Any, *, create: bool = False) -> float:
        """
        Get the remaining cooldown time with a key instead of the context.

        Note:
            The preferred way to get the cooldown system is to use `get_cooldown` as it will use the context to get the correct key.

        Args:
            key: The key to get the cooldown system for
            create: Whether to create a new cooldown system if one does not exist

        """
        cooldown = self.get_cooldown_with_key(key, create=create)
        if cooldown is not None:
            return cooldown.get_cooldown_time()
        return 0

    async def on_cooldown(self, context: "BaseContext") -> bool:
        """
        Returns the cooldown state of the command.

        Args:
            context: The context of the command

        Returns:
            boolean state if the command is on cooldown or not

        """
        cooldown = await self.get_cooldown(context)
        return cooldown.on_cooldown()

    async def reset_all(self) -> None:
        """
        Resets this cooldown system to its initial state.

        !!! warning     To be clear, this will reset **all** cooldowns
        for this command to their initial states

        """
        # this doesnt need to be async, but for consistency, it is
        self.cooldown_repositories = {}

    async def reset(self, context: "BaseContext") -> None:
        """
        Resets the cooldown for the bucket of which invoked this command.

        Args:
            context: The context of the command

        """
        cooldown = await self.get_cooldown(context)
        cooldown.reset()

    def reset_with_key(self, key: Any) -> bool:
        """
        Resets the cooldown for the bucket associated with the provided key.

        Note:
            The preferred way to reset the cooldown system is to use `reset_cooldown` as it will use the context to reset the correct cooldown.

        Args:
            key: The key to reset the cooldown system for

        Returns:
            True if the key existed and was reset successfully, False if the key didn't exist.

        """
        cooldown = self.get_cooldown_with_key(key)
        if cooldown is not None:
            cooldown.reset()
            return True
        return False

acquire_token(context) async

Attempt to acquire a token for a command to run. Uses the context of the command to use the correct CooldownStrategy.

Parameters:

Name Type Description Default
context BaseContext

The context of the command

required

Returns:

Type Description
bool

True if a token was acquired, False if not

Source code in interactions/models/internal/cooldowns.py
370
371
372
373
374
375
376
377
378
379
380
381
382
383
async def acquire_token(self, context: "BaseContext") -> bool:
    """
    Attempt to acquire a token for a command to run. Uses the context of the command to use the correct CooldownStrategy.

    Args:
        context: The context of the command

    Returns:
        True if a token was acquired, False if not

    """
    cooldown = await self.get_cooldown(context)

    return cooldown.acquire_token()

get_cooldown_time(context) async

Get the remaining cooldown time.

Parameters:

Name Type Description Default
context BaseContext

The context of the command

required

Returns:

Type Description
float

remaining cooldown time, will return 0 if the cooldown has not been reached

Source code in interactions/models/internal/cooldowns.py
385
386
387
388
389
390
391
392
393
394
395
396
397
async def get_cooldown_time(self, context: "BaseContext") -> float:
    """
    Get the remaining cooldown time.

    Args:
        context: The context of the command

    Returns:
        remaining cooldown time, will return 0 if the cooldown has not been reached

    """
    cooldown = await self.get_cooldown(context)
    return cooldown.get_cooldown_time()

get_cooldown_time_with_key(key, *, create=False)

Get the remaining cooldown time with a key instead of the context.

Note

The preferred way to get the cooldown system is to use get_cooldown as it will use the context to get the correct key.

Parameters:

Name Type Description Default
key Any

The key to get the cooldown system for

required
create bool

Whether to create a new cooldown system if one does not exist

False
Source code in interactions/models/internal/cooldowns.py
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
def get_cooldown_time_with_key(self, key: Any, *, create: bool = False) -> float:
    """
    Get the remaining cooldown time with a key instead of the context.

    Note:
        The preferred way to get the cooldown system is to use `get_cooldown` as it will use the context to get the correct key.

    Args:
        key: The key to get the cooldown system for
        create: Whether to create a new cooldown system if one does not exist

    """
    cooldown = self.get_cooldown_with_key(key, create=create)
    if cooldown is not None:
        return cooldown.get_cooldown_time()
    return 0

get_cooldown_with_key(key, *, create=False)

Get the cooldown system for the command.

Note

The preferred way to get the cooldown system is to use get_cooldown as it will use the context to get the correct key.

Parameters:

Name Type Description Default
key Any

The key to get the cooldown system for

required
create bool

Whether to create a new cooldown system if one does not exist

False
Source code in interactions/models/internal/cooldowns.py
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def get_cooldown_with_key(self, key: Any, *, create: bool = False) -> typing.Optional["CooldownSystem"]:
    """
    Get the cooldown system for the command.

    Note:
        The preferred way to get the cooldown system is to use `get_cooldown` as it will use the context to get the correct key.

    Args:
        key: The key to get the cooldown system for
        create: Whether to create a new cooldown system if one does not exist

    """
    if key not in self.cooldown_repositories and create:
        cooldown = self.cooldown_system(self.rate, self.interval)
        self.cooldown_repositories[key] = cooldown
        return cooldown
    return self.cooldown_repositories.get(key)

on_cooldown(context) async

Returns the cooldown state of the command.

Parameters:

Name Type Description Default
context BaseContext

The context of the command

required

Returns:

Type Description
bool

boolean state if the command is on cooldown or not

Source code in interactions/models/internal/cooldowns.py
416
417
418
419
420
421
422
423
424
425
426
427
428
async def on_cooldown(self, context: "BaseContext") -> bool:
    """
    Returns the cooldown state of the command.

    Args:
        context: The context of the command

    Returns:
        boolean state if the command is on cooldown or not

    """
    cooldown = await self.get_cooldown(context)
    return cooldown.on_cooldown()

reset(context) async

Resets the cooldown for the bucket of which invoked this command.

Parameters:

Name Type Description Default
context BaseContext

The context of the command

required
Source code in interactions/models/internal/cooldowns.py
441
442
443
444
445
446
447
448
449
450
async def reset(self, context: "BaseContext") -> None:
    """
    Resets the cooldown for the bucket of which invoked this command.

    Args:
        context: The context of the command

    """
    cooldown = await self.get_cooldown(context)
    cooldown.reset()

reset_all() async

Resets this cooldown system to its initial state.

!!! warning To be clear, this will reset all cooldowns for this command to their initial states

Source code in interactions/models/internal/cooldowns.py
430
431
432
433
434
435
436
437
438
439
async def reset_all(self) -> None:
    """
    Resets this cooldown system to its initial state.

    !!! warning     To be clear, this will reset **all** cooldowns
    for this command to their initial states

    """
    # this doesnt need to be async, but for consistency, it is
    self.cooldown_repositories = {}

reset_with_key(key)

Resets the cooldown for the bucket associated with the provided key.

Note

The preferred way to reset the cooldown system is to use reset_cooldown as it will use the context to reset the correct cooldown.

Parameters:

Name Type Description Default
key Any

The key to reset the cooldown system for

required

Returns:

Type Description
bool

True if the key existed and was reset successfully, False if the key didn't exist.

Source code in interactions/models/internal/cooldowns.py
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
def reset_with_key(self, key: Any) -> bool:
    """
    Resets the cooldown for the bucket associated with the provided key.

    Note:
        The preferred way to reset the cooldown system is to use `reset_cooldown` as it will use the context to reset the correct cooldown.

    Args:
        key: The key to reset the cooldown system for

    Returns:
        True if the key existed and was reset successfully, False if the key didn't exist.

    """
    cooldown = self.get_cooldown_with_key(key)
    if cooldown is not None:
        cooldown.reset()
        return True
    return False

CooldownSystem

A basic cooldown strategy that allows a specific number of commands to be executed within a given interval. Once the rate is reached, no more tokens can be acquired until the interval has passed.

Attributes:

Name Type Description
rate int

The number of commands allowed per interval.

interval float

The time window (in seconds) within which the allowed number of commands can be executed.

Example Use-case

This strategy is useful for scenarios where you want to limit the number of times a command can be executed within a fixed time frame, such as preventing command spamming or limiting API calls.

Source code in interactions/models/internal/cooldowns.py
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
class CooldownSystem:
    """
    A basic cooldown strategy that allows a specific number of commands to be executed within a given interval. Once the rate is reached, no more tokens can be acquired until the interval has passed.

    Attributes:
        rate: The number of commands allowed per interval.
        interval: The time window (in seconds) within which the allowed number of commands can be executed.

    ??? tip "Example Use-case"
        This strategy is useful for scenarios where you want to limit the number of times a command can be executed within a fixed time frame, such as preventing command spamming or limiting API calls.

    """

    __slots__ = "rate", "interval", "opened", "_tokens"

    def __init__(self, rate: int, interval: float) -> None:
        self.rate: int = rate
        self.interval: float = interval
        self.opened: float = 0.0

        self._tokens: int = self.rate

        # sanity checks
        if self.rate == 0:
            raise ValueError("Cooldown rate must be greater than 0")
        if self.interval == 0:
            raise ValueError("Cooldown interval must be greater than 0")

    def reset(self) -> None:
        """Resets the tokens for this cooldown."""
        self._tokens = self.rate
        self.opened = 0.0

    def on_cooldown(self) -> bool:
        """
        Returns the cooldown state of the command.

        Returns:
            boolean state if the command is on cooldown or not

        """
        self.determine_cooldown()

        return self._tokens == 0

    def acquire_token(self) -> bool:
        """
        Attempt to acquire a token for a command to run.

        Returns:
            True if a token was acquired, False if not

        """
        self.determine_cooldown()

        if self._tokens == 0:
            return False
        if self._tokens == self.rate:
            self.opened = time.time()
        self._tokens -= 1

        return True

    def get_cooldown_time(self) -> float:
        """
        Returns how long until the cooldown will reset.

        Returns:
            remaining cooldown time, will return 0 if the cooldown has not been reached

        """
        self.determine_cooldown()
        return 0 if self._tokens != 0 else self.interval - (time.time() - self.opened)

    def determine_cooldown(self) -> None:
        """Determines the state of the cooldown system."""
        c_time = time.time()

        if c_time > self.opened + self.interval:
            # cooldown has expired, reset the cooldown
            self.reset()

acquire_token()

Attempt to acquire a token for a command to run.

Returns:

Type Description
bool

True if a token was acquired, False if not

Source code in interactions/models/internal/cooldowns.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def acquire_token(self) -> bool:
    """
    Attempt to acquire a token for a command to run.

    Returns:
        True if a token was acquired, False if not

    """
    self.determine_cooldown()

    if self._tokens == 0:
        return False
    if self._tokens == self.rate:
        self.opened = time.time()
    self._tokens -= 1

    return True

determine_cooldown()

Determines the state of the cooldown system.

Source code in interactions/models/internal/cooldowns.py
139
140
141
142
143
144
145
def determine_cooldown(self) -> None:
    """Determines the state of the cooldown system."""
    c_time = time.time()

    if c_time > self.opened + self.interval:
        # cooldown has expired, reset the cooldown
        self.reset()

get_cooldown_time()

Returns how long until the cooldown will reset.

Returns:

Type Description
float

remaining cooldown time, will return 0 if the cooldown has not been reached

Source code in interactions/models/internal/cooldowns.py
128
129
130
131
132
133
134
135
136
137
def get_cooldown_time(self) -> float:
    """
    Returns how long until the cooldown will reset.

    Returns:
        remaining cooldown time, will return 0 if the cooldown has not been reached

    """
    self.determine_cooldown()
    return 0 if self._tokens != 0 else self.interval - (time.time() - self.opened)

on_cooldown()

Returns the cooldown state of the command.

Returns:

Type Description
bool

boolean state if the command is on cooldown or not

Source code in interactions/models/internal/cooldowns.py
 98
 99
100
101
102
103
104
105
106
107
108
def on_cooldown(self) -> bool:
    """
    Returns the cooldown state of the command.

    Returns:
        boolean state if the command is on cooldown or not

    """
    self.determine_cooldown()

    return self._tokens == 0

reset()

Resets the tokens for this cooldown.

Source code in interactions/models/internal/cooldowns.py
93
94
95
96
def reset(self) -> None:
    """Resets the tokens for this cooldown."""
    self._tokens = self.rate
    self.opened = 0.0

ExponentialBackoffSystem

Bases: CooldownSystem

An exponential backoff cooldown strategy that doubles the interval between allowed commands after each failed attempt, up to a maximum interval.

Attributes:

Name Type Description
rate

The number of commands allowed per interval.

interval

The initial time window (in seconds) within which the allowed number of commands can be executed.

max_interval

The maximum time window (in seconds) between allowed commands.

multiplier

The multiplier to apply to the interval after each failed attempt.

Example Use-case

This strategy is useful for scenarios where you want to progressively slow down repeated attempts at a command, such as preventing brute force attacks or limiting retries on failed operations.

Source code in interactions/models/internal/cooldowns.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
class ExponentialBackoffSystem(CooldownSystem):
    """
    An exponential backoff cooldown strategy that doubles the interval between allowed commands after each failed attempt, up to a maximum interval.

    Attributes:
        rate: The number of commands allowed per interval.
        interval: The initial time window (in seconds) within which the allowed number of commands can be executed.
        max_interval: The maximum time window (in seconds) between allowed commands.
        multiplier: The multiplier to apply to the interval after each failed attempt.

    ??? tip "Example Use-case"
        This strategy is useful for scenarios where you want to progressively slow down repeated attempts at a command, such as preventing brute force attacks or limiting retries on failed operations.

    """

    def __init__(self, rate: int, interval: float, max_interval: float, multiplier: float = 2) -> None:
        super().__init__(rate, interval)
        self.max_interval = max_interval
        self.multiplier = multiplier

    def determine_cooldown(self) -> None:
        c_time = time.time()

        if c_time > self.opened + self.interval:
            if self.interval < self.max_interval:
                self.interval *= self.multiplier
            self.reset()

LeakyBucketSystem

Bases: CooldownSystem

A leaky bucket cooldown strategy that gradually replenishes tokens over time, allowing commands to be executed as long as there are available tokens in the bucket.

Attributes:

Name Type Description
rate

The number of tokens generated per interval.

interval

The time window (in seconds) within which the tokens are generated.

Example Use-case

This strategy is useful for scenarios where you want to allow a steady flow of commands to be executed while preventing sudden bursts, such as rate limiting API calls or managing user interactions in a chatbot.

Source code in interactions/models/internal/cooldowns.py
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
class LeakyBucketSystem(CooldownSystem):
    """
    A leaky bucket cooldown strategy that gradually replenishes tokens over time, allowing commands to be executed as long as there are available tokens in the bucket.

    Attributes:
        rate: The number of tokens generated per interval.
        interval: The time window (in seconds) within which the tokens are generated.

    ??? tip "Example Use-case"
        This strategy is useful for scenarios where you want to allow a steady flow of commands to be executed while preventing sudden bursts, such as rate limiting API calls or managing user interactions in a chatbot.

    """

    def determine_cooldown(self) -> None:
        c_time = time.time()

        tokens_to_recover = (c_time - self.opened) / self.interval
        if tokens_to_recover >= 1:
            self._tokens = min(self.rate, self._tokens + int(tokens_to_recover))
            self.opened = c_time

MaxConcurrency

Limits how many instances of a command may be running concurrently.

Attributes:

Name Type Description
bucket Buckets

The bucket this concurrency applies to

concurrent int

The maximum number of concurrent instances permitted to

wait bool

Should we wait until a instance is available

Source code in interactions/models/internal/cooldowns.py
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
class MaxConcurrency:
    """
    Limits how many instances of a command may be running concurrently.

    Attributes:
        bucket Buckets: The bucket this concurrency applies to
        concurrent int: The maximum number of concurrent instances permitted to
        wait bool: Should we wait until a instance is available

    """

    def __init__(self, concurrent: int, concurrency_bucket: Buckets, wait: bool = False) -> None:
        self.bucket: Buckets = concurrency_bucket
        self.concurrency_repository: Dict = {}
        self.concurrent: int = concurrent
        self.wait = wait

    async def get_semaphore(self, context: "BaseContext") -> asyncio.Semaphore:
        """
        Get the semaphore associated with the given context.

        Args:
            context: The commands context

        Returns:
            A semaphore object

        """
        key = await self.bucket(context)

        if key not in self.concurrency_repository:
            semaphore = asyncio.Semaphore(self.concurrent)
            self.concurrency_repository[key] = semaphore
            return semaphore
        return self.concurrency_repository.get(key)

    async def acquire(self, context: "BaseContext") -> bool:
        """
        Acquire an instance of the semaphore.

        Args:
            context:The context of the command
        Returns:
            If the semaphore was successfully acquired

        """
        semaphore = await self.get_semaphore(context)

        if not self.wait and semaphore.locked():
            return False
        return await semaphore.acquire()

    async def release(self, context: "BaseContext") -> None:
        """
        Release the semaphore.

        Args:
            context: The context of the command

        """
        semaphore = await self.get_semaphore(context)

        semaphore.release()

acquire(context) async

Acquire an instance of the semaphore.

Parameters:

Name Type Description Default
context BaseContext

The context of the command

required

Returns:

Type Description
bool

If the semaphore was successfully acquired

Source code in interactions/models/internal/cooldowns.py
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
async def acquire(self, context: "BaseContext") -> bool:
    """
    Acquire an instance of the semaphore.

    Args:
        context:The context of the command
    Returns:
        If the semaphore was successfully acquired

    """
    semaphore = await self.get_semaphore(context)

    if not self.wait and semaphore.locked():
        return False
    return await semaphore.acquire()

get_semaphore(context) async

Get the semaphore associated with the given context.

Parameters:

Name Type Description Default
context BaseContext

The commands context

required

Returns:

Type Description
asyncio.Semaphore

A semaphore object

Source code in interactions/models/internal/cooldowns.py
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
async def get_semaphore(self, context: "BaseContext") -> asyncio.Semaphore:
    """
    Get the semaphore associated with the given context.

    Args:
        context: The commands context

    Returns:
        A semaphore object

    """
    key = await self.bucket(context)

    if key not in self.concurrency_repository:
        semaphore = asyncio.Semaphore(self.concurrent)
        self.concurrency_repository[key] = semaphore
        return semaphore
    return self.concurrency_repository.get(key)

release(context) async

Release the semaphore.

Parameters:

Name Type Description Default
context BaseContext

The context of the command

required
Source code in interactions/models/internal/cooldowns.py
525
526
527
528
529
530
531
532
533
534
535
async def release(self, context: "BaseContext") -> None:
    """
    Release the semaphore.

    Args:
        context: The context of the command

    """
    semaphore = await self.get_semaphore(context)

    semaphore.release()

SlidingWindowSystem

Bases: CooldownSystem

A sliding window cooldown strategy that allows a specific number of commands to be executed within a rolling time window.

The cooldown incrementally resets as commands fall outside of the window.

Attributes:

Name Type Description
rate int

The number of commands allowed per interval.

interval float

The time window (in seconds) within which the allowed number of commands can be executed.

Example Use-case

This strategy is useful for scenarios where you want to limit the rate of commands executed over a continuous time window, such as ensuring consistent usage of resources or controlling chat bot response frequency.

Source code in interactions/models/internal/cooldowns.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
class SlidingWindowSystem(CooldownSystem):
    """
    A sliding window cooldown strategy that allows a specific number of commands to be executed within a rolling time window.

    The cooldown incrementally resets as commands fall outside of the window.

    Attributes:
        rate: The number of commands allowed per interval.
        interval: The time window (in seconds) within which the allowed number of commands can be executed.

    ??? tip "Example Use-case"
        This strategy is useful for scenarios where you want to limit the rate of commands executed over a continuous time window, such as ensuring consistent usage of resources or controlling chat bot response frequency.

    """

    __slots__ = "rate", "interval", "timestamps"

    def __init__(self, rate: int, interval: float) -> None:
        self.rate: int = rate
        self.interval: float = interval
        self.timestamps: list[float] = []

        # sanity checks
        if self.rate == 0:
            raise ValueError("Cooldown rate must be greater than 0")
        if self.interval == 0:
            raise ValueError("Cooldown interval must be greater than 0")

    def on_cooldown(self) -> bool:
        """
        Returns the cooldown state of the command.

        Returns:
            boolean state if the command is on cooldown or not

        """
        self._trim()

        return len(self.timestamps) >= self.rate

    def acquire_token(self) -> bool:
        """
        Attempt to acquire a token for a command to run.

        Returns:
            True if a token was acquired, False if not

        """
        self._trim()

        if len(self.timestamps) >= self.rate:
            return False

        self.timestamps.append(time.time())

        return True

    def get_cooldown_time(self) -> float:
        """
        Returns how long until the cooldown will reset.

        Returns:
            remaining cooldown time, will return 0 if the cooldown has not been reached

        """
        self._trim()

        if len(self.timestamps) < self.rate:
            return 0

        return self.timestamps[0] + self.interval - time.time()

    def reset(self) -> None:
        """Resets the timestamps for this cooldown."""
        self.timestamps = []

    def _trim(self) -> None:
        """Removes all timestamps that are outside the current interval."""
        cutoff = time.time() - self.interval

        while self.timestamps and self.timestamps[0] < cutoff:
            self.timestamps.pop(0)

acquire_token()

Attempt to acquire a token for a command to run.

Returns:

Type Description
bool

True if a token was acquired, False if not

Source code in interactions/models/internal/cooldowns.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def acquire_token(self) -> bool:
    """
    Attempt to acquire a token for a command to run.

    Returns:
        True if a token was acquired, False if not

    """
    self._trim()

    if len(self.timestamps) >= self.rate:
        return False

    self.timestamps.append(time.time())

    return True

get_cooldown_time()

Returns how long until the cooldown will reset.

Returns:

Type Description
float

remaining cooldown time, will return 0 if the cooldown has not been reached

Source code in interactions/models/internal/cooldowns.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
def get_cooldown_time(self) -> float:
    """
    Returns how long until the cooldown will reset.

    Returns:
        remaining cooldown time, will return 0 if the cooldown has not been reached

    """
    self._trim()

    if len(self.timestamps) < self.rate:
        return 0

    return self.timestamps[0] + self.interval - time.time()

on_cooldown()

Returns the cooldown state of the command.

Returns:

Type Description
bool

boolean state if the command is on cooldown or not

Source code in interactions/models/internal/cooldowns.py
176
177
178
179
180
181
182
183
184
185
186
def on_cooldown(self) -> bool:
    """
    Returns the cooldown state of the command.

    Returns:
        boolean state if the command is on cooldown or not

    """
    self._trim()

    return len(self.timestamps) >= self.rate

reset()

Resets the timestamps for this cooldown.

Source code in interactions/models/internal/cooldowns.py
220
221
222
def reset(self) -> None:
    """Resets the timestamps for this cooldown."""
    self.timestamps = []

TokenBucketSystem

Bases: CooldownSystem

A token bucket cooldown strategy that generates tokens at a specific rate up to a burst rate, allowing commands to be executed as long as there are available tokens in the bucket.

Attributes:

Name Type Description
rate

The number of tokens generated per interval.

interval

The time window (in seconds) within which the tokens are generated.

burst_rate

The maximum number of tokens that can be held in the bucket at any given time.

Example Use-case

This strategy is useful for scenarios where you want to allow a burst of commands to be executed while limiting the overall rate, such as handling peak traffic in an API or permitting rapid user interactions in a game.

Source code in interactions/models/internal/cooldowns.py
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
class TokenBucketSystem(CooldownSystem):
    """
    A token bucket cooldown strategy that generates tokens at a specific rate up to a burst rate, allowing commands to be executed as long as there are available tokens in the bucket.

    Attributes:
        rate: The number of tokens generated per interval.
        interval: The time window (in seconds) within which the tokens are generated.
        burst_rate: The maximum number of tokens that can be held in the bucket at any given time.

    ??? tip "Example Use-case"
        This strategy is useful for scenarios where you want to allow a burst of commands to be executed while limiting the overall rate, such as handling peak traffic in an API or permitting rapid user interactions in a game.

    """

    def __init__(self, rate: int, interval: float, burst_rate: int) -> None:
        super().__init__(rate, interval)
        self.burst_rate = burst_rate

    def determine_cooldown(self) -> None:
        c_time = time.time()

        tokens_to_recover = (c_time - self.opened) / self.interval
        if tokens_to_recover >= 1:
            self._tokens = min(self.burst_rate, self._tokens + int(tokens_to_recover))
            self.opened = c_time

dm_only()

This command may only be ran in a dm.

Source code in interactions/models/internal/checks.py
86
87
88
89
90
91
92
def dm_only() -> TYPE_CHECK_FUNCTION:
    """This command may only be ran in a dm."""

    async def check(ctx: BaseContext) -> bool:
        return ctx.guild is None

    return check

guild_only()

This command may only be ran in a guild.

Source code in interactions/models/internal/checks.py
77
78
79
80
81
82
83
def guild_only() -> TYPE_CHECK_FUNCTION:
    """This command may only be ran in a guild."""

    async def check(ctx: BaseContext) -> bool:
        return ctx.guild is not None

    return check

has_any_role(*roles)

Checks if the user has any of the given roles.

Parameters:

Name Type Description Default
*roles Snowflake_Type | Role

The Role(s) or role id(s) to check for

()
Source code in interactions/models/internal/checks.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def has_any_role(*roles: Snowflake_Type | Role) -> TYPE_CHECK_FUNCTION:
    """
    Checks if the user has any of the given roles.

    Args:
        *roles: The Role(s) or role id(s) to check for

    """

    async def check(ctx: BaseContext) -> bool:
        if ctx.guild is None:
            return False

        author: Member = ctx.author  # pyright: ignore [reportGeneralTypeIssues]
        return any((author.has_role(to_snowflake(r)) for r in roles))

    return check

has_id(user_id)

Checks if the author has the desired ID.

Parameters:

Name Type Description Default
user_id int

id of the user to check for

required
Source code in interactions/models/internal/checks.py
50
51
52
53
54
55
56
57
58
59
60
61
62
def has_id(user_id: int) -> TYPE_CHECK_FUNCTION:
    """
    Checks if the author has the desired ID.

    Args:
        user_id: id of the user to check for

    """

    async def check(ctx: BaseContext) -> bool:
        return ctx.author.id == user_id

    return check

has_role(role)

Check if the user has the given role.

Parameters:

Name Type Description Default
role Snowflake_Type | Role

The Role or role id to check for

required
Source code in interactions/models/internal/checks.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def has_role(role: Snowflake_Type | Role) -> TYPE_CHECK_FUNCTION:
    """
    Check if the user has the given role.

    Args:
        role: The Role or role id to check for

    """

    async def check(ctx: BaseContext) -> bool:
        if ctx.guild is None:
            return False
        author: Member = ctx.author  # pyright: ignore [reportGeneralTypeIssues]
        return author.has_role(role)

    return check

is_owner()

Checks if the author is the owner of the bot. This respects the client.owner_ids list.

Source code in interactions/models/internal/checks.py
65
66
67
68
69
70
71
72
73
74
def is_owner() -> TYPE_CHECK_FUNCTION:
    """Checks if the author is the owner of the bot. This respects the `client.owner_ids` list."""

    async def check(ctx: BaseContext) -> bool:
        _owner_ids: set = ctx.bot.owner_ids.copy()
        if ctx.bot.app.team:
            [_owner_ids.add(m.id) for m in ctx.bot.app.team.members]
        return ctx.author.id in _owner_ids

    return check

AutoDefer

Automatically defer application commands for you!

Source code in interactions/models/internal/auto_defer.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class AutoDefer:
    """Automatically defer application commands for you!"""

    enabled: bool = attrs.field(repr=False, default=False)
    """Whether or not auto-defer is enabled"""

    ephemeral: bool = attrs.field(repr=False, default=False)
    """Should the command be deferred as ephemeral or not"""

    time_until_defer: float = attrs.field(repr=False, default=1.5)
    """How long to wait before automatically deferring"""

    async def __call__(self, ctx: "InteractionContext") -> None:
        if self.enabled:
            if self.time_until_defer > 0:
                loop = asyncio.get_event_loop()
                loop.call_later(self.time_until_defer, loop.create_task, self.defer(ctx))
            else:
                await ctx.defer(ephemeral=self.ephemeral)

    async def defer(self, ctx: "InteractionContext") -> None:
        """Defer the command"""
        if not ctx.responded or not ctx.deferred:
            with contextlib.suppress(AlreadyDeferred, NotFound, BadRequest, HTTPException):
                await ctx.defer(ephemeral=self.ephemeral)

enabled: bool = attrs.field(repr=False, default=False) class-attribute

Whether or not auto-defer is enabled

ephemeral: bool = attrs.field(repr=False, default=False) class-attribute

Should the command be deferred as ephemeral or not

time_until_defer: float = attrs.field(repr=False, default=1.5) class-attribute

How long to wait before automatically deferring

defer(ctx) async

Defer the command

Source code in interactions/models/internal/auto_defer.py
36
37
38
39
40
async def defer(self, ctx: "InteractionContext") -> None:
    """Defer the command"""
    if not ctx.responded or not ctx.deferred:
        with contextlib.suppress(AlreadyDeferred, NotFound, BadRequest, HTTPException):
            await ctx.defer(ephemeral=self.ephemeral)

slash_attachment_option(description, required=False, name=None)

Annotates an argument as an attachment type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
name Optional[str]

The name of the option. Defaults to the name of the argument

None
Source code in interactions/models/internal/annotations/slash.py
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
def slash_attachment_option(
    description: str,
    required: bool = False,
    name: Optional[str] = None,
) -> Type["Attachment"]:
    """
    Annotates an argument as an attachment type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        name: The name of the option. Defaults to the name of the argument

    """
    return SlashCommandOption(
        name=name,
        description=description,
        required=required,
        type=models.OptionType.ATTACHMENT,
    )

slash_bool_option(description, required=False, name=None)

Annotates an argument as a boolean type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
name Optional[str]

The name of the option. Defaults to the name of the argument

None
Source code in interactions/models/internal/annotations/slash.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def slash_bool_option(
    description: str,
    required: bool = False,
    name: Optional[str] = None,
) -> Type[bool]:
    """
    Annotates an argument as a boolean type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        name: The name of the option. Defaults to the name of the argument

    """
    return SlashCommandOption(
        name=name,
        description=description,
        required=required,
        type=models.OptionType.BOOLEAN,
    )

slash_channel_option(description, required=False, autocomplete=False, choices=None, channel_types=None, name=None)

Annotates an argument as a channel type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
choices List[Union[SlashCommandChoice, dict]] | None

The choices allowed by this command

None
channel_types Optional[list[Union[ChannelType, int]]]

The types of channel allowed by this option

None
name Optional[str]

The name of the option. Defaults to the name of the argument

None
Source code in interactions/models/internal/annotations/slash.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def slash_channel_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union["SlashCommandChoice", dict]] | None = None,
    channel_types: Optional[list[Union["ChannelType", int]]] = None,
    name: Optional[str] = None,
) -> Type["BaseChannel"]:
    """
    Annotates an argument as a channel type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        choices: The choices allowed by this command
        channel_types: The types of channel allowed by this option
        name: The name of the option. Defaults to the name of the argument

    """
    return SlashCommandOption(
        name=name,
        description=description,
        required=required,
        autocomplete=autocomplete,
        choices=choices or [],
        channel_types=channel_types,
        type=models.OptionType.CHANNEL,
    )

slash_float_option(description, required=False, autocomplete=False, choices=None, min_value=None, max_value=None, name=None)

Annotates an argument as a float type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
choices List[Union[SlashCommandChoice, dict]] | None

The choices allowed by this command

None
min_value Optional[float]

The minimum number allowed

None
max_value Optional[float]

The maximum number allowed

None
name Optional[str]

The name of the option. Defaults to the name of the argument

None
Source code in interactions/models/internal/annotations/slash.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def slash_float_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union["SlashCommandChoice", dict]] | None = None,
    min_value: Optional[float] = None,
    max_value: Optional[float] = None,
    name: Optional[str] = None,
) -> Type[float]:
    """
    Annotates an argument as a float type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        choices: The choices allowed by this command
        min_value: The minimum number allowed
        max_value: The maximum number allowed
        name: The name of the option. Defaults to the name of the argument

    """
    return SlashCommandOption(
        name=name,
        description=description,
        required=required,
        autocomplete=autocomplete,
        choices=choices or [],
        max_value=max_value,
        min_value=min_value,
        type=models.OptionType.NUMBER,
    )

slash_int_option(description, required=False, autocomplete=False, choices=None, min_value=None, max_value=None, name=None)

Annotates an argument as a integer type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
choices List[Union[SlashCommandChoice, dict]] | None

The choices allowed by this command

None
min_value Optional[float]

The minimum number allowed

None
max_value Optional[float]

The maximum number allowed

None
name Optional[str]

The name of the option. Defaults to the name of the argument

None
Source code in interactions/models/internal/annotations/slash.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def slash_int_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union["SlashCommandChoice", dict]] | None = None,
    min_value: Optional[float] = None,
    max_value: Optional[float] = None,
    name: Optional[str] = None,
) -> Type[int]:
    """
    Annotates an argument as a integer type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        choices: The choices allowed by this command
        min_value: The minimum number allowed
        max_value: The maximum number allowed
        name: The name of the option. Defaults to the name of the argument

    """
    return SlashCommandOption(
        name=name,
        description=description,
        required=required,
        autocomplete=autocomplete,
        choices=choices or [],
        max_value=max_value,
        min_value=min_value,
        type=models.OptionType.INTEGER,
    )

slash_mentionable_option(description, required=False, autocomplete=False, choices=None, name=None)

Annotates an argument as a mentionable type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
choices List[Union[SlashCommandChoice, dict]] | None

The choices allowed by this command

None
name Optional[str]

The name of the option. Defaults to the name of the argument

None
Source code in interactions/models/internal/annotations/slash.py
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
def slash_mentionable_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union["SlashCommandChoice", dict]] | None = None,
    name: Optional[str] = None,
) -> Type[Union["Role", "BaseChannel", "User", "Member"]]:
    """
    Annotates an argument as a mentionable type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        choices: The choices allowed by this command
        name: The name of the option. Defaults to the name of the argument

    """
    return SlashCommandOption(
        name=name,
        description=description,
        required=required,
        autocomplete=autocomplete,
        choices=choices or [],
        type=models.OptionType.MENTIONABLE,
    )

slash_role_option(description, required=False, autocomplete=False, choices=None, name=None)

Annotates an argument as a role type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
choices List[Union[SlashCommandChoice, dict]] | None

The choices allowed by this command

None
name Optional[str]

The name of the option. Defaults to the name of the argument

None
Source code in interactions/models/internal/annotations/slash.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
def slash_role_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union["SlashCommandChoice", dict]] | None = None,
    name: Optional[str] = None,
) -> Type["Role"]:
    """
    Annotates an argument as a role type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        choices: The choices allowed by this command
        name: The name of the option. Defaults to the name of the argument

    """
    return SlashCommandOption(
        name=name,
        description=description,
        required=required,
        autocomplete=autocomplete,
        choices=choices or [],
        type=models.OptionType.ROLE,
    )

slash_str_option(description, required=False, autocomplete=False, choices=None, min_length=None, max_length=None, name=None)

Annotates an argument as a string type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
choices List[Union[SlashCommandChoice, dict]] | None

The choices allowed by this command

None
min_length Optional[int]

The minimum length of text a user can input.

None
max_length Optional[int]

The maximum length of text a user can input.

None
name Optional[str]

The name of the option. Defaults to the name of the argument

None
Source code in interactions/models/internal/annotations/slash.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def slash_str_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union["SlashCommandChoice", dict]] | None = None,
    min_length: Optional[int] = None,
    max_length: Optional[int] = None,
    name: Optional[str] = None,
) -> Type[str]:
    """
    Annotates an argument as a string type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        choices: The choices allowed by this command
        min_length: The minimum length of text a user can input.
        max_length: The maximum length of text a user can input.
        name: The name of the option. Defaults to the name of the argument

    """
    return SlashCommandOption(
        name=name,
        description=description,
        required=required,
        autocomplete=autocomplete,
        choices=choices or [],
        max_length=max_length,
        min_length=min_length,
        type=models.OptionType.STRING,
    )

slash_user_option(description, required=False, autocomplete=False, name=None)

Annotates an argument as a user type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
name Optional[str]

The name of the option. Defaults to the name of the argument

None
Source code in interactions/models/internal/annotations/slash.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def slash_user_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    name: Optional[str] = None,
) -> Type[Union["User", "Member"]]:
    """
    Annotates an argument as a user type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        name: The name of the option. Defaults to the name of the argument

    """
    return SlashCommandOption(
        name=name,
        description=description,
        required=required,
        autocomplete=autocomplete,
        type=models.OptionType.USER,
    )

ConsumeRest = Annotated[T, CONSUME_REST_MARKER] module-attribute

A special marker type alias to mark an argument in a prefixed command to consume the rest of the arguments.

MODEL_TO_CONVERTER: dict[type, type[Converter]] = {SnowflakeObject: SnowflakeConverter, BaseChannel: BaseChannelConverter, DMChannel: DMChannelConverter, DM: DMConverter, DMGroup: DMGroupConverter, GuildChannel: GuildChannelConverter, GuildNews: GuildNewsConverter, GuildCategory: GuildCategoryConverter, GuildText: GuildTextConverter, ThreadChannel: ThreadChannelConverter, GuildNewsThread: GuildNewsThreadConverter, GuildPublicThread: GuildPublicThreadConverter, GuildPrivateThread: GuildPrivateThreadConverter, VoiceChannel: VoiceChannelConverter, GuildVoice: GuildVoiceConverter, GuildStageVoice: GuildStageVoiceConverter, TYPE_ALL_CHANNEL: BaseChannelConverter, TYPE_DM_CHANNEL: DMChannelConverter, TYPE_GUILD_CHANNEL: GuildChannelConverter, TYPE_THREAD_CHANNEL: ThreadChannelConverter, TYPE_VOICE_CHANNEL: VoiceChannelConverter, TYPE_MESSAGEABLE_CHANNEL: MessageableChannelConverter, User: UserConverter, Member: MemberConverter, Message: MessageConverter, Guild: GuildConverter, Role: RoleConverter, PartialEmoji: PartialEmojiConverter, CustomEmoji: CustomEmojiConverter} module-attribute

A dictionary mapping of interactions objects to their corresponding converters.

ChannelConverter

Bases: IDConverter[T_co]

The base converter for channel objects.

Source code in interactions/models/internal/converters.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
class ChannelConverter(IDConverter[T_co]):
    """The base converter for channel objects."""

    def _check(self, result: BaseChannel) -> bool:
        return True

    async def convert(self, ctx: BaseContext, argument: str) -> T_co:
        """
        Converts a given string to a Channel object.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By channel mention.

        3. By name - the bot will search in a guild if the context has it, otherwise it will search globally.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            BaseChannel: The converted object.
            The channel type will be of the type the converter represents.

        """
        match = self._get_id_match(argument) or re.match(r"<#([0-9]{15,})>$", argument)
        result = None

        if match:
            result = await ctx.bot.fetch_channel(int(match.group(1)))
        elif ctx.guild:
            result = next((c for c in ctx.guild.channels if c.name == argument), None)
        else:
            result = next((c for c in ctx.bot.cache.channel_cache.values() if c.name == argument), None)

        if not result:
            raise BadArgument(f'Channel "{argument}" not found.')

        if self._check(result):
            return result  # type: ignore

        raise BadArgument(f'Channel "{argument}" not found.')

convert(ctx, argument) async

Converts a given string to a Channel object.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By channel mention.

  3. By name - the bot will search in a guild if the context has it, otherwise it will search globally.

Parameters:

Name Type Description Default
ctx BaseContext

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
BaseChannel T_co

The converted object.

T_co

The channel type will be of the type the converter represents.

Source code in interactions/models/internal/converters.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
async def convert(self, ctx: BaseContext, argument: str) -> T_co:
    """
    Converts a given string to a Channel object.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By channel mention.

    3. By name - the bot will search in a guild if the context has it, otherwise it will search globally.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        BaseChannel: The converted object.
        The channel type will be of the type the converter represents.

    """
    match = self._get_id_match(argument) or re.match(r"<#([0-9]{15,})>$", argument)
    result = None

    if match:
        result = await ctx.bot.fetch_channel(int(match.group(1)))
    elif ctx.guild:
        result = next((c for c in ctx.guild.channels if c.name == argument), None)
    else:
        result = next((c for c in ctx.bot.cache.channel_cache.values() if c.name == argument), None)

    if not result:
        raise BadArgument(f'Channel "{argument}" not found.')

    if self._check(result):
        return result  # type: ignore

    raise BadArgument(f'Channel "{argument}" not found.')

CustomEmojiConverter

Bases: IDConverter[CustomEmoji]

Converts a string argument to a CustomEmoji object.

Source code in interactions/models/internal/converters.py
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
class CustomEmojiConverter(IDConverter[CustomEmoji]):
    """Converts a string argument to a CustomEmoji object."""

    async def convert(self, ctx: BaseContext, argument: str) -> CustomEmoji:
        """
        Converts a given string to a CustomEmoji object.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By the emoji string format.

        3. By name.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            CustomEmoji: The converted object.

        """
        if not ctx.guild:
            raise BadArgument("This command cannot be used in private messages.")

        match = self._get_id_match(argument) or re.match(r"<a?:[a-zA-Z0-9\_]{1,32}:([0-9]{15,})>$", argument)
        result = None

        if match:
            result = await ctx.guild.fetch_custom_emoji(int(match.group(1)))
        else:
            if ctx.bot.cache.enable_emoji_cache:
                emojis = ctx.bot.cache.emoji_cache.values()  # type: ignore
                result = next((e for e in emojis if e.name == argument))

            if not result:
                emojis = await ctx.guild.fetch_all_custom_emojis()
                result = next((e for e in emojis if e.name == argument))

        if not result:
            raise BadArgument(f'Emoji "{argument}" not found.')

        return result

convert(ctx, argument) async

Converts a given string to a CustomEmoji object.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By the emoji string format.

  3. By name.

Parameters:

Name Type Description Default
ctx BaseContext

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
CustomEmoji CustomEmoji

The converted object.

Source code in interactions/models/internal/converters.py
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
async def convert(self, ctx: BaseContext, argument: str) -> CustomEmoji:
    """
    Converts a given string to a CustomEmoji object.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By the emoji string format.

    3. By name.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        CustomEmoji: The converted object.

    """
    if not ctx.guild:
        raise BadArgument("This command cannot be used in private messages.")

    match = self._get_id_match(argument) or re.match(r"<a?:[a-zA-Z0-9\_]{1,32}:([0-9]{15,})>$", argument)
    result = None

    if match:
        result = await ctx.guild.fetch_custom_emoji(int(match.group(1)))
    else:
        if ctx.bot.cache.enable_emoji_cache:
            emojis = ctx.bot.cache.emoji_cache.values()  # type: ignore
            result = next((e for e in emojis if e.name == argument))

        if not result:
            emojis = await ctx.guild.fetch_all_custom_emojis()
            result = next((e for e in emojis if e.name == argument))

    if not result:
        raise BadArgument(f'Emoji "{argument}" not found.')

    return result

Greedy

Bases: List[T]

A special marker class to mark an argument in a prefixed command to repeatedly convert until it fails to convert an argument.

Source code in interactions/models/internal/converters.py
581
582
class Greedy(List[T]):
    """A special marker class to mark an argument in a prefixed command to repeatedly convert until it fails to convert an argument."""

GuildConverter

Bases: IDConverter[Guild]

Converts a string argument to a Guild object.

Source code in interactions/models/internal/converters.py
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
class GuildConverter(IDConverter[Guild]):
    """Converts a string argument to a Guild object."""

    async def convert(self, ctx: BaseContext, argument: str) -> Guild:
        """
        Converts a given string to a Guild object.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By name.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            Guild: The converted object.

        """
        match = self._get_id_match(argument)
        result = None

        if match:
            result = await ctx.bot.fetch_guild(int(match.group(1)))
        else:
            result = next((g for g in ctx.bot.guilds if g.name == argument), None)

        if not result:
            raise BadArgument(f'Guild "{argument}" not found.')

        return result

convert(ctx, argument) async

Converts a given string to a Guild object.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By name.

Parameters:

Name Type Description Default
ctx BaseContext

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
Guild Guild

The converted object.

Source code in interactions/models/internal/converters.py
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
async def convert(self, ctx: BaseContext, argument: str) -> Guild:
    """
    Converts a given string to a Guild object.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By name.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        Guild: The converted object.

    """
    match = self._get_id_match(argument)
    result = None

    if match:
        result = await ctx.bot.fetch_guild(int(match.group(1)))
    else:
        result = next((g for g in ctx.bot.guilds if g.name == argument), None)

    if not result:
        raise BadArgument(f'Guild "{argument}" not found.')

    return result

IDConverter

Bases: Converter[T_co]

The base converter for objects that have snowflake IDs.

Source code in interactions/models/internal/converters.py
104
105
106
107
108
109
class IDConverter(Converter[T_co]):
    """The base converter for objects that have snowflake IDs."""

    @staticmethod
    def _get_id_match(argument: str) -> Optional[re.Match[str]]:
        return _ID_REGEX.match(argument)

MemberConverter

Bases: IDConverter[Member]

Converts a string argument to a Member object.

Source code in interactions/models/internal/converters.py
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
class MemberConverter(IDConverter[Member]):
    """Converts a string argument to a Member object."""

    def _get_member_from_list(self, members: list[Member], argument: str) -> Optional[Member]:
        # sourcery skip: assign-if-exp
        result = None
        if len(argument) > 5 and argument[-5] == "#":
            result = next((m for m in members if m.user.tag == argument), None)

        if not result:
            result = next(
                (m for m in members if m.display_name == argument or m.user.username == argument),
                None,
            )

        return result

    async def convert(self, ctx: BaseContext, argument: str) -> Member:
        """
        Converts a given string to a Member object. This will only work in guilds.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By mention.

        3. By username + tag (ex User#1234).

        4. By nickname.

        5. By username.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            Member: The converted object.

        """
        if not ctx.guild:
            raise BadArgument("This command cannot be used in private messages.")

        match = self._get_id_match(argument) or re.match(r"<@!?([0-9]{15,})>$", argument)
        result = None

        if match:
            result = await ctx.guild.fetch_member(int(match.group(1)))
        elif ctx.guild.chunked:
            result = self._get_member_from_list(ctx.guild.members, argument)
        else:
            query = argument
            if len(argument) > 5 and argument[-5] == "#":
                query, _, _ = argument.rpartition("#")

            members = await ctx.guild.search_members(query, limit=100)
            result = self._get_member_from_list(members, argument)

        if not result:
            raise BadArgument(f'Member "{argument}" not found.')

        return result

convert(ctx, argument) async

Converts a given string to a Member object. This will only work in guilds.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By mention.

  3. By username + tag (ex User#1234).

  4. By nickname.

  5. By username.

Parameters:

Name Type Description Default
ctx BaseContext

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
Member Member

The converted object.

Source code in interactions/models/internal/converters.py
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
async def convert(self, ctx: BaseContext, argument: str) -> Member:
    """
    Converts a given string to a Member object. This will only work in guilds.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By mention.

    3. By username + tag (ex User#1234).

    4. By nickname.

    5. By username.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        Member: The converted object.

    """
    if not ctx.guild:
        raise BadArgument("This command cannot be used in private messages.")

    match = self._get_id_match(argument) or re.match(r"<@!?([0-9]{15,})>$", argument)
    result = None

    if match:
        result = await ctx.guild.fetch_member(int(match.group(1)))
    elif ctx.guild.chunked:
        result = self._get_member_from_list(ctx.guild.members, argument)
    else:
        query = argument
        if len(argument) > 5 and argument[-5] == "#":
            query, _, _ = argument.rpartition("#")

        members = await ctx.guild.search_members(query, limit=100)
        result = self._get_member_from_list(members, argument)

    if not result:
        raise BadArgument(f'Member "{argument}" not found.')

    return result

MessageConverter

Bases: Converter[Message]

Converts a string argument to a Message object.

Source code in interactions/models/internal/converters.py
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
class MessageConverter(Converter[Message]):
    """Converts a string argument to a Message object."""

    # either just the id or <chan_id>-<mes_id>, a format you can get by shift clicking "copy id"
    _ID_REGEX = re.compile(r"(?:(?P<channel_id>[0-9]{15,})-)?(?P<message_id>[0-9]{15,})")
    # of course, having a way to get it from a link is nice
    _MESSAGE_LINK_REGEX = re.compile(
        r"https?://[\S]*?discord(?:app)?\.com/channels/(?P<guild_id>[0-9]{15,}|@me)/(?P<channel_id>[0-9]{15,})/(?P<message_id>[0-9]{15,})\/?$"
    )

    async def convert(self, ctx: BaseContext, argument: str) -> Message:
        """
        Converts a given string to a Message object.

        The lookup strategy is as follows:

        1. By raw snowflake ID. The message must be in the same channel as the context.

        2. By message + channel ID in the format of "{Channel ID}-{Message ID}". This can be obtained by shift clicking "Copy ID" when Developer Mode is enabled.

        3. By message link.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            Message: The converted object.

        """
        match = self._ID_REGEX.match(argument) or self._MESSAGE_LINK_REGEX.match(argument)
        if not match:
            raise BadArgument(f'Message "{argument}" not found.')

        data = match.groupdict()

        message_id = data["message_id"]
        channel_id = int(data["channel_id"]) if data.get("channel_id") else ctx.channel.id

        # this guild checking is technically unnecessary, but we do it just in case
        # it means a user cant just provide an invalid guild id and still get a message
        guild_id = data["guild_id"] if data.get("guild_id") else ctx.guild_id
        guild_id = int(guild_id) if guild_id != "@me" else None

        try:
            # this takes less possible requests than getting the guild and/or channel
            mes = await ctx.bot.cache.fetch_message(channel_id, message_id)
            if mes._guild_id != guild_id:
                raise BadArgument(f'Message "{argument}" not found.')
            return mes
        except Forbidden as e:
            raise BadArgument(f"Cannot read messages for <#{channel_id}>.") from e
        except HTTPException as e:
            raise BadArgument(f'Message "{argument}" not found.') from e

convert(ctx, argument) async

Converts a given string to a Message object.

The lookup strategy is as follows:

  1. By raw snowflake ID. The message must be in the same channel as the context.

  2. By message + channel ID in the format of "{Channel ID}-{Message ID}". This can be obtained by shift clicking "Copy ID" when Developer Mode is enabled.

  3. By message link.

Parameters:

Name Type Description Default
ctx BaseContext

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
Message Message

The converted object.

Source code in interactions/models/internal/converters.py
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
async def convert(self, ctx: BaseContext, argument: str) -> Message:
    """
    Converts a given string to a Message object.

    The lookup strategy is as follows:

    1. By raw snowflake ID. The message must be in the same channel as the context.

    2. By message + channel ID in the format of "{Channel ID}-{Message ID}". This can be obtained by shift clicking "Copy ID" when Developer Mode is enabled.

    3. By message link.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        Message: The converted object.

    """
    match = self._ID_REGEX.match(argument) or self._MESSAGE_LINK_REGEX.match(argument)
    if not match:
        raise BadArgument(f'Message "{argument}" not found.')

    data = match.groupdict()

    message_id = data["message_id"]
    channel_id = int(data["channel_id"]) if data.get("channel_id") else ctx.channel.id

    # this guild checking is technically unnecessary, but we do it just in case
    # it means a user cant just provide an invalid guild id and still get a message
    guild_id = data["guild_id"] if data.get("guild_id") else ctx.guild_id
    guild_id = int(guild_id) if guild_id != "@me" else None

    try:
        # this takes less possible requests than getting the guild and/or channel
        mes = await ctx.bot.cache.fetch_message(channel_id, message_id)
        if mes._guild_id != guild_id:
            raise BadArgument(f'Message "{argument}" not found.')
        return mes
    except Forbidden as e:
        raise BadArgument(f"Cannot read messages for <#{channel_id}>.") from e
    except HTTPException as e:
        raise BadArgument(f'Message "{argument}" not found.') from e

NoArgumentConverter

Bases: Converter[T_co]

An indicator class for special type of converters that only uses the Context.

This is mainly needed for prefixed commands, as arguments will be "eaten up" by converters otherwise.

Source code in interactions/models/internal/converters.py
74
75
76
77
78
79
class NoArgumentConverter(Converter[T_co]):
    """
    An indicator class for special type of converters that only uses the Context.

    This is mainly needed for prefixed commands, as arguments will be "eaten up" by converters otherwise.
    """

PartialEmojiConverter

Bases: IDConverter[PartialEmoji]

Converts a string argument to a PartialEmoji object.

Source code in interactions/models/internal/converters.py
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
class PartialEmojiConverter(IDConverter[PartialEmoji]):
    """Converts a string argument to a PartialEmoji object."""

    async def convert(self, ctx: BaseContext, argument: str) -> PartialEmoji:
        """
        Converts a given string to a PartialEmoji object.

        This converter only accepts emoji strings.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            PartialEmoji: The converted object.

        """
        if match := re.match(r"<a?:[a-zA-Z0-9\_]{1,32}:([0-9]{15,})>$", argument):
            emoji_animated = bool(match[1])
            emoji_name = match[2]
            emoji_id = int(match[3])

            return PartialEmoji(id=emoji_id, name=emoji_name, animated=emoji_animated)  # type: ignore

        raise BadArgument(f'Couldn\'t convert "{argument}" to {PartialEmoji.__name__}.')

convert(ctx, argument) async

Converts a given string to a PartialEmoji object.

This converter only accepts emoji strings.

Parameters:

Name Type Description Default
ctx BaseContext

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
PartialEmoji PartialEmoji

The converted object.

Source code in interactions/models/internal/converters.py
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
async def convert(self, ctx: BaseContext, argument: str) -> PartialEmoji:
    """
    Converts a given string to a PartialEmoji object.

    This converter only accepts emoji strings.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        PartialEmoji: The converted object.

    """
    if match := re.match(r"<a?:[a-zA-Z0-9\_]{1,32}:([0-9]{15,})>$", argument):
        emoji_animated = bool(match[1])
        emoji_name = match[2]
        emoji_id = int(match[3])

        return PartialEmoji(id=emoji_id, name=emoji_name, animated=emoji_animated)  # type: ignore

    raise BadArgument(f'Couldn\'t convert "{argument}" to {PartialEmoji.__name__}.')

RoleConverter

Bases: IDConverter[Role]

Converts a string argument to a Role object.

Source code in interactions/models/internal/converters.py
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
class RoleConverter(IDConverter[Role]):
    """Converts a string argument to a Role object."""

    async def convert(self, ctx: BaseContext, argument: str) -> Role:
        """
        Converts a given string to a Role object.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By mention.

        3. By name.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            Role: The converted object.

        """
        if not ctx.guild:
            raise BadArgument("This command cannot be used in private messages.")

        match = self._get_id_match(argument) or re.match(r"<@&([0-9]{15,})>$", argument)
        result = None

        if match:
            result = await ctx.guild.fetch_role(int(match.group(1)))
        else:
            result = next((r for r in ctx.guild.roles if r.name == argument), None)

        if not result:
            raise BadArgument(f'Role "{argument}" not found.')

        return result

convert(ctx, argument) async

Converts a given string to a Role object.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By mention.

  3. By name.

Parameters:

Name Type Description Default
ctx BaseContext

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
Role Role

The converted object.

Source code in interactions/models/internal/converters.py
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
async def convert(self, ctx: BaseContext, argument: str) -> Role:
    """
    Converts a given string to a Role object.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By mention.

    3. By name.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        Role: The converted object.

    """
    if not ctx.guild:
        raise BadArgument("This command cannot be used in private messages.")

    match = self._get_id_match(argument) or re.match(r"<@&([0-9]{15,})>$", argument)
    result = None

    if match:
        result = await ctx.guild.fetch_role(int(match.group(1)))
    else:
        result = next((r for r in ctx.guild.roles if r.name == argument), None)

    if not result:
        raise BadArgument(f'Role "{argument}" not found.')

    return result

SnowflakeConverter

Bases: IDConverter[SnowflakeObject]

Converts a string argument to a SnowflakeObject.

Source code in interactions/models/internal/converters.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
class SnowflakeConverter(IDConverter[SnowflakeObject]):
    """Converts a string argument to a SnowflakeObject."""

    async def convert(self, ctx: BaseContext, argument: str) -> SnowflakeObject:
        """
        Converts a given string to a SnowflakeObject.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By role or channel mention.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            SnowflakeObject: The converted object.

        """
        match = self._get_id_match(argument) or re.match(r"<(?:@(?:!|&)?|#)([0-9]{15,})>$", argument)

        if match is None:
            raise BadArgument(argument)

        return SnowflakeObject(int(match.group(1)))  # type: ignore

convert(ctx, argument) async

Converts a given string to a SnowflakeObject.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By role or channel mention.

Parameters:

Name Type Description Default
ctx BaseContext

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
SnowflakeObject SnowflakeObject

The converted object.

Source code in interactions/models/internal/converters.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
async def convert(self, ctx: BaseContext, argument: str) -> SnowflakeObject:
    """
    Converts a given string to a SnowflakeObject.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By role or channel mention.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        SnowflakeObject: The converted object.

    """
    match = self._get_id_match(argument) or re.match(r"<(?:@(?:!|&)?|#)([0-9]{15,})>$", argument)

    if match is None:
        raise BadArgument(argument)

    return SnowflakeObject(int(match.group(1)))  # type: ignore

UserConverter

Bases: IDConverter[User]

Converts a string argument to a User object.

Source code in interactions/models/internal/converters.py
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
class UserConverter(IDConverter[User]):
    """Converts a string argument to a User object."""

    async def convert(self, ctx: BaseContext, argument: str) -> User:
        """
        Converts a given string to a User object.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By mention.

        3. By username + tag (ex User#1234).

        4. By username.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            User: The converted object.

        """
        match = self._get_id_match(argument) or re.match(r"<@!?([0-9]{15,})>$", argument)
        result = None

        if match:
            result = await ctx.bot.fetch_user(int(match.group(1)))
        else:
            if len(argument) > 5 and argument[-5] == "#":
                result = next((u for u in ctx.bot.cache.user_cache.values() if u.tag == argument), None)

            if not result:
                result = next((u for u in ctx.bot.cache.user_cache.values() if u.username == argument), None)

        if not result:
            raise BadArgument(f'User "{argument}" not found.')

        return result

convert(ctx, argument) async

Converts a given string to a User object.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By mention.

  3. By username + tag (ex User#1234).

  4. By username.

Parameters:

Name Type Description Default
ctx BaseContext

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
User User

The converted object.

Source code in interactions/models/internal/converters.py
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
async def convert(self, ctx: BaseContext, argument: str) -> User:
    """
    Converts a given string to a User object.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By mention.

    3. By username + tag (ex User#1234).

    4. By username.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        User: The converted object.

    """
    match = self._get_id_match(argument) or re.match(r"<@!?([0-9]{15,})>$", argument)
    result = None

    if match:
        result = await ctx.bot.fetch_user(int(match.group(1)))
    else:
        if len(argument) > 5 and argument[-5] == "#":
            result = next((u for u in ctx.bot.cache.user_cache.values() if u.tag == argument), None)

        if not result:
            result = next((u for u in ctx.bot.cache.user_cache.values() if u.username == argument), None)

    if not result:
        raise BadArgument(f'User "{argument}" not found.')

    return result

Listener

Bases: CallbackObject

Source code in interactions/models/internal/listener.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
class Listener(CallbackObject):
    event: str
    """Name of the event to listen to."""
    callback: AsyncCallable
    """Coroutine to call when the event is triggered."""
    is_default_listener: bool
    """Whether this listener is provided automatically by the library, and might be unwanted by users."""
    disable_default_listeners: bool
    """Whether this listener supersedes default listeners.  If true, any default listeners will be unregistered."""
    delay_until_ready: bool
    """whether to delay the event until the client is ready"""

    def __init__(
        self,
        func: AsyncCallable,
        event: str,
        *,
        delay_until_ready: bool = False,
        is_default_listener: bool = False,
        disable_default_listeners: bool = False,
        pass_event_object: Absent[bool] = MISSING,
    ) -> None:
        super().__init__()

        if is_default_listener:
            disable_default_listeners = False

        self.event = event
        self.callback = func
        self.delay_until_ready = delay_until_ready
        self.is_default_listener = is_default_listener
        self.disable_default_listeners = disable_default_listeners

        self._params = inspect.signature(func).parameters.copy()
        self.pass_event_object = pass_event_object
        self.warned_no_event_arg = False

    def __repr__(self) -> str:
        return f"<Listener event={self.event!r} callback={self.callback!r}>"

    @classmethod
    def create(
        cls,
        event_name: Absent[str | BaseEvent] = MISSING,
        *,
        delay_until_ready: bool = False,
        is_default_listener: bool = False,
        disable_default_listeners: bool = False,
    ) -> Callable[[AsyncCallable], "Listener"]:
        """
        Decorator for creating an event listener.

        Args:
            event_name: The name of the event to listen to. If left blank, event name will be inferred from the function name or parameter.
            delay_until_ready: Whether to delay the listener until the client is ready.
            is_default_listener: Whether this listener is provided automatically by the library, and might be unwanted by users.
            disable_default_listeners: Whether this listener supersedes default listeners.  If true, any default listeners will be unregistered.


        Returns:
            A listener object.

        """

        def wrapper(coro: AsyncCallable) -> "Listener":
            if not asyncio.iscoroutinefunction(coro):
                raise TypeError("Listener must be a coroutine")

            name = event_name

            if name is MISSING:
                for typehint in coro.__annotations__.values():
                    if (
                        inspect.isclass(typehint)
                        and issubclass(typehint, BaseEvent)
                        and typehint.__name__ != "RawGatewayEvent"
                    ):
                        name = typehint.__name__
                        break

                if not name:
                    name = coro.__name__

            return cls(
                coro,
                get_event_name(name),
                delay_until_ready=delay_until_ready,
                is_default_listener=is_default_listener,
                disable_default_listeners=disable_default_listeners,
            )

        return wrapper

    def lazy_parse_params(self):
        """Process the parameters of this listener."""
        if self.pass_event_object is not MISSING:
            return

        if self.has_binding:
            # discard the first parameter, which is the class instance
            self._params = list(self._params.values())[1:]

        self.pass_event_object = len(self._params) != 0

create(event_name=MISSING, *, delay_until_ready=False, is_default_listener=False, disable_default_listeners=False) classmethod

Decorator for creating an event listener.

Parameters:

Name Type Description Default
event_name Absent[str | BaseEvent]

The name of the event to listen to. If left blank, event name will be inferred from the function name or parameter.

MISSING
delay_until_ready bool

Whether to delay the listener until the client is ready.

False
is_default_listener bool

Whether this listener is provided automatically by the library, and might be unwanted by users.

False
disable_default_listeners bool

Whether this listener supersedes default listeners. If true, any default listeners will be unregistered.

False

Returns:

Type Description
Callable[[AsyncCallable], Listener]

A listener object.

Source code in interactions/models/internal/listener.py
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
@classmethod
def create(
    cls,
    event_name: Absent[str | BaseEvent] = MISSING,
    *,
    delay_until_ready: bool = False,
    is_default_listener: bool = False,
    disable_default_listeners: bool = False,
) -> Callable[[AsyncCallable], "Listener"]:
    """
    Decorator for creating an event listener.

    Args:
        event_name: The name of the event to listen to. If left blank, event name will be inferred from the function name or parameter.
        delay_until_ready: Whether to delay the listener until the client is ready.
        is_default_listener: Whether this listener is provided automatically by the library, and might be unwanted by users.
        disable_default_listeners: Whether this listener supersedes default listeners.  If true, any default listeners will be unregistered.


    Returns:
        A listener object.

    """

    def wrapper(coro: AsyncCallable) -> "Listener":
        if not asyncio.iscoroutinefunction(coro):
            raise TypeError("Listener must be a coroutine")

        name = event_name

        if name is MISSING:
            for typehint in coro.__annotations__.values():
                if (
                    inspect.isclass(typehint)
                    and issubclass(typehint, BaseEvent)
                    and typehint.__name__ != "RawGatewayEvent"
                ):
                    name = typehint.__name__
                    break

            if not name:
                name = coro.__name__

        return cls(
            coro,
            get_event_name(name),
            delay_until_ready=delay_until_ready,
            is_default_listener=is_default_listener,
            disable_default_listeners=disable_default_listeners,
        )

    return wrapper

lazy_parse_params()

Process the parameters of this listener.

Source code in interactions/models/internal/listener.py
106
107
108
109
110
111
112
113
114
115
def lazy_parse_params(self):
    """Process the parameters of this listener."""
    if self.pass_event_object is not MISSING:
        return

    if self.has_binding:
        # discard the first parameter, which is the class instance
        self._params = list(self._params.values())[1:]

    self.pass_event_object = len(self._params) != 0

listen(event_name=MISSING, *, delay_until_ready=False, is_default_listener=False, disable_default_listeners=False)

Decorator to make a function an event listener.

Parameters:

Name Type Description Default
event_name Absent[str | type[BaseEvent]]

The name of the event to listen to. If left blank, event name will be inferred from the function name or parameter.

MISSING
delay_until_ready bool

Whether to delay the listener until the client is ready.

False
is_default_listener bool

Whether this listener is provided automatically by the library, and might be unwanted by users.

False
disable_default_listeners bool

Whether this listener supersedes default listeners. If true, any default listeners will be unregistered.

False

Returns:

Type Description
Callable[[AsyncCallable], Listener]

A listener object.

Source code in interactions/models/internal/listener.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
def listen(
    event_name: Absent[str | type[BaseEvent]] = MISSING,
    *,
    delay_until_ready: bool = False,
    is_default_listener: bool = False,
    disable_default_listeners: bool = False,
) -> Callable[[AsyncCallable], Listener]:
    """
    Decorator to make a function an event listener.

    Args:
        event_name: The name of the event to listen to. If left blank, event name will be inferred from the function name or parameter.
        delay_until_ready: Whether to delay the listener until the client is ready.
        is_default_listener: Whether this listener is provided automatically by the library, and might be unwanted by users.
        disable_default_listeners: Whether this listener supersedes default listeners.  If true, any default listeners will be unregistered.


    Returns:
        A listener object.

    """
    return Listener.create(
        event_name,
        delay_until_ready=delay_until_ready,
        is_default_listener=is_default_listener,
        disable_default_listeners=disable_default_listeners,
    )

LocalisedField

An object that enables support for localising fields.

Supported locales: https://discord.com/developers/docs/reference#locales

Source code in interactions/models/internal/localisation.py
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
@attrs.define(eq=False, order=False, hash=False, slots=False)
class LocalisedField:
    """
    An object that enables support for localising fields.

    Supported locales: https://discord.com/developers/docs/reference#locales
    """

    default_locale: str = attrs.field(repr=False, default=const.default_locale)

    bulgarian: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "bg"})
    chinese_china: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "zh-CN"})
    chinese_taiwan: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "zh-TW"})
    croatian: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "hr"})
    czech: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "cs"})
    danish: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "da"})
    dutch: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "nl"})
    english_uk: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "en-GB"})
    english_us: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "en-US"})
    finnish: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "fi"})
    french: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "fr"})
    german: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "de"})
    greek: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "el"})
    hindi: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "hi"})
    hungarian: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "hu"})
    italian: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "it"})
    japanese: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "ja"})
    korean: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "ko"})
    lithuanian: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "lt"})
    norwegian: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "no"})
    polish: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "pl"})
    portuguese_brazilian: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "pt-BR"})
    romanian_romania: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "ro"})
    russian: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "ru"})
    spanish: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "es-ES"})
    swedish: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "sv-SE"})
    thai: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "th"})
    turkish: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "tr"})
    ukrainian: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "uk"})
    vietnamese: str | None = attrs.field(repr=False, default=None, metadata={"locale-code": "vi"})

    def __str__(self) -> str:
        return str(self.default)

    def __bool__(self) -> bool:
        return self.default is not None

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__}: default_locale={self.default_locale}, value='{self}'>"

    @cached_property
    def _code_mapping(self) -> dict:
        """Generates a lookup table for this object on demand to allow for value retrieval with locale codes"""
        data = []
        for attr in self.__attrs_attrs__:
            if attr.name != self.default_locale:
                if code := attr.metadata.get("locale-code"):
                    data.append((code, attr.name))
        return dict(data)

    @property
    def default(self) -> str:
        """The default value based on the CONST default_locale"""
        return getattr(self, self.default_locale)

    def get_locale(self, locale: str) -> str:
        """
        Get the value for the specified locale. Supports locale-codes and locale names.

        Args:
            locale: The locale to fetch

        Returns:
            The localised string, or the default value

        """
        if val := getattr(self, locale, None):
            # Attempt to retrieve an attribute with the specified locale
            return val
        if attr := self._code_mapping.get(locale):
            # assume the locale is a code, and attempt to find an attribute with that code
            if val := getattr(self, attr, None):
                # if the value isn't None, return
                return val

        # no value was found, return default
        return self.default

    @classmethod
    def converter(cls, value: str | None) -> "LocalisedField":
        if isinstance(value, LocalisedField):
            return value
        obj = cls()
        if value:
            obj.__setattr__(obj.default_locale, str(value))

        return obj

    @default_locale.validator
    def _default_locale_validator(self, _, value: str) -> None:
        try:
            getattr(self, value)
        except AttributeError:
            raise ValueError(f"`{value}` is not a supported default localisation") from None

    def as_dict(self) -> str:
        return str(self)

    def to_locale_dict(self) -> dict:
        data = {}
        for attr in self.__attrs_attrs__:
            if attr.name != self.default_locale and "locale-code" in attr.metadata:
                if val := getattr(self, attr.name):
                    data[attr.metadata["locale-code"]] = val

        if not data:
            data = None  # handle discord being stupid
        return data

default: str property

The default value based on the CONST default_locale

get_locale(locale)

Get the value for the specified locale. Supports locale-codes and locale names.

Parameters:

Name Type Description Default
locale str

The locale to fetch

required

Returns:

Type Description
str

The localised string, or the default value

Source code in interactions/models/internal/localisation.py
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def get_locale(self, locale: str) -> str:
    """
    Get the value for the specified locale. Supports locale-codes and locale names.

    Args:
        locale: The locale to fetch

    Returns:
        The localised string, or the default value

    """
    if val := getattr(self, locale, None):
        # Attempt to retrieve an attribute with the specified locale
        return val
    if attr := self._code_mapping.get(locale):
        # assume the locale is a code, and attempt to find an attribute with that code
        if val := getattr(self, attr, None):
            # if the value isn't None, return
            return val

    # no value was found, return default
    return self.default

BaseTrigger

Bases: ABC

Source code in interactions/models/internal/tasks/triggers.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class BaseTrigger(ABC):
    last_call_time: datetime

    def __new__(cls, *args, **kwargs) -> "BaseTrigger":
        new_cls = super().__new__(cls)
        new_cls.last_call_time = datetime.now()
        return new_cls

    def __or__(self, other: "BaseTrigger") -> "OrTrigger":
        return OrTrigger(self, other)

    def reschedule(self) -> None:
        """Update the last call time to now"""
        self.last_call_time = datetime.now()

    def set_last_call_time(self, call_time: datetime) -> None:
        self.last_call_time = call_time

    @abstractmethod
    def next_fire(self) -> datetime | None:
        """
        Return the next datetime to fire on.

        Returns:
            Datetime if one can be determined. If no datetime can be determined, return None

        """
        ...

next_fire() abstractmethod

Return the next datetime to fire on.

Returns:

Type Description
datetime | None

Datetime if one can be determined. If no datetime can be determined, return None

Source code in interactions/models/internal/tasks/triggers.py
31
32
33
34
35
36
37
38
39
40
@abstractmethod
def next_fire(self) -> datetime | None:
    """
    Return the next datetime to fire on.

    Returns:
        Datetime if one can be determined. If no datetime can be determined, return None

    """
    ...

reschedule()

Update the last call time to now

Source code in interactions/models/internal/tasks/triggers.py
24
25
26
def reschedule(self) -> None:
    """Update the last call time to now"""
    self.last_call_time = datetime.now()

CronTrigger

Bases: BaseTrigger

Trigger the task based on a cron schedule.

Attributes:

Name Type Description
cron str

The cron schedule, use https://crontab.guru for help

tz datetime

Whether or not to use UTC for the time (uses timezone information)

Source code in interactions/models/internal/tasks/triggers.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
class CronTrigger(BaseTrigger):
    """
    Trigger the task based on a cron schedule.

    Attributes:
        cron str: The cron schedule, use https://crontab.guru for help
        tz datetime: Whether or not to use UTC for the time (uses timezone information)

    """

    def __init__(self, cron: str, tz: "_TzInfo" = timezone.utc) -> None:
        self.cron = cron
        self.tz = tz

    def next_fire(self) -> datetime | None:
        return croniter(self.cron, self.last_call_time.astimezone(self.tz)).next(datetime)

DateTrigger

Bases: BaseTrigger

Trigger the task once, when the specified datetime is reached.

Attributes:

Name Type Description
target_datetime datetime

A datetime representing the date/time to run this task

Source code in interactions/models/internal/tasks/triggers.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class DateTrigger(BaseTrigger):
    """
    Trigger the task once, when the specified datetime is reached.

    Attributes:
        target_datetime datetime: A datetime representing the date/time to run this task

    """

    def __init__(self, target_datetime: datetime) -> None:
        self.target = target_datetime

    def next_fire(self) -> datetime | None:
        return self.target if datetime.now() < self.target else None

IntervalTrigger

Bases: BaseTrigger

Trigger the task every set interval.

Attributes:

Name Type Description
seconds Union[int, float]

How many seconds between intervals

minutes Union[int, float]

How many minutes between intervals

hours Union[int, float]

How many hours between intervals

days Union[int, float]

How many days between intervals

weeks Union[int, float]

How many weeks between intervals

Source code in interactions/models/internal/tasks/triggers.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class IntervalTrigger(BaseTrigger):
    """
    Trigger the task every set interval.

    Attributes:
        seconds Union[int, float]: How many seconds between intervals
        minutes Union[int, float]: How many minutes between intervals
        hours Union[int, float]: How many hours between intervals
        days Union[int, float]: How many days between intervals
        weeks Union[int, float]: How many weeks between intervals

    """

    _t = int | float

    def __init__(self, seconds: _t = 0, minutes: _t = 0, hours: _t = 0, days: _t = 0, weeks: _t = 0) -> None:
        self.delta = timedelta(days=days, seconds=seconds, minutes=minutes, hours=hours, weeks=weeks)

        # lazy check for negatives
        if (datetime.now() + self.delta) < datetime.now():
            raise ValueError("Interval values must result in a time in the future!")

    def next_fire(self) -> datetime | None:
        return self.last_call_time + self.delta

OrTrigger

Bases: BaseTrigger

Trigger a task when any sub-trigger is fulfilled.

Source code in interactions/models/internal/tasks/triggers.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
class OrTrigger(BaseTrigger):
    """Trigger a task when any sub-trigger is fulfilled."""

    def __init__(self, *trigger: BaseTrigger) -> None:
        self.triggers: list[BaseTrigger] = list(trigger)
        self.current_trigger: BaseTrigger = None

    def set_last_call_time(self, call_time: datetime) -> None:
        self.current_trigger.last_call_time = call_time

    def _get_delta(self, d: BaseTrigger) -> timedelta:
        next_fire = d.next_fire()
        return abs(next_fire - self.last_call_time) if next_fire else timedelta.max

    def __or__(self, other: "BaseTrigger") -> "OrTrigger":
        self.triggers.append(other)
        return self

    def _set_current_trigger(self) -> BaseTrigger | None:
        self.current_trigger = self.triggers[0] if len(self.triggers) == 1 else min(self.triggers, key=self._get_delta)
        return self.current_trigger

    def next_fire(self) -> datetime | None:
        return self.current_trigger.next_fire() if self._set_current_trigger() else None

Task

Create an asynchronous background tasks. Tasks allow you to run code according to a trigger object.

A task's trigger must inherit from BaseTrigger.

Attributes:

Name Type Description
callback Callable

The function to be called when the trigger is triggered.

trigger BaseTrigger

The trigger object that determines when the task should run.

task Optional[_Task]

The task object that is running the trigger loop.

iteration int

The number of times the task has run.

Source code in interactions/models/internal/tasks/task.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
class Task:
    """
    Create an asynchronous background tasks. Tasks allow you to run code according to a trigger object.

    A task's trigger must inherit from `BaseTrigger`.

    Attributes:
        callback (Callable): The function to be called when the trigger is triggered.
        trigger (BaseTrigger): The trigger object that determines when the task should run.
        task (Optional[_Task]): The task object that is running the trigger loop.
        iteration (int): The number of times the task has run.

    """

    callback: Callable
    trigger: BaseTrigger
    task: _Task | None
    _stop: asyncio.Event
    iteration: int

    def __init__(self, callback: Callable, trigger: BaseTrigger) -> None:
        self.callback = callback
        self.trigger = trigger
        self._stop = asyncio.Event()
        self.task = None
        self.iteration = 0

    @property
    def started(self) -> bool:
        """Whether the task is started"""
        return self.task is not None

    @property
    def running(self) -> bool:
        """Whether the task is running"""
        return self.task is not None and not self.task.done()

    @property
    def done(self) -> bool:
        """Whether the task is done/finished"""
        return self.task is not None and self.task.done()

    @property
    def next_run(self) -> datetime | None:
        """Get the next datetime this task will run."""
        return self.trigger.next_fire() if self.running else None

    @property
    def delta_until_run(self) -> timedelta | None:
        """Get the time until the next run of this task."""
        if not self.running:
            return None

        next_run = self.next_run
        return next_run - datetime.now() if next_run is not None else None

    def on_error_sentry_hook(self, error: Exception) -> None:
        """A dummy method for interactions.ext.sentry to hook"""

    def on_error(self, error: Exception) -> None:
        """Error handler for this task. Called when an exception is raised during execution of the task."""
        self.on_error_sentry_hook(error)
        interactions.Client.default_error_handler("Task", error)

    async def __call__(self, *args, **kwargs) -> None:
        try:
            if inspect.iscoroutinefunction(self.callback):
                val = await self.callback(*args, **kwargs)
            else:
                val = self.callback(*args, **kwargs)

            if isinstance(val, BaseTrigger):
                self.reschedule(val)
        except Exception as e:
            self.on_error(e)

    def _fire(self, fire_time: datetime, *args, **kwargs) -> asyncio.Task:
        """Called when the task is being fired."""
        self.trigger.set_last_call_time(fire_time)
        task = asyncio.create_task(self(*args, **kwargs))
        self.iteration += 1
        return task

    async def _task_loop(self, *args, **kwargs) -> None:
        """The main task loop to fire the task at the specified time based on triggers configured."""
        while not self._stop.is_set():
            fire_time = self.trigger.next_fire()
            if fire_time is None:
                return self.stop()

            future = asyncio.create_task(self._stop.wait())
            timeout = (fire_time - datetime.now(tz=fire_time.tzinfo)).total_seconds()
            done, _ = await asyncio.wait([future], timeout=timeout, return_when=asyncio.FIRST_COMPLETED)
            if future in done:
                return None

            self._fire(fire_time, *args, **kwargs)

    def start(self, *args, **kwargs) -> None:
        """Start this task."""
        try:
            self.trigger.reschedule()
            self._stop.clear()
            self.task = asyncio.create_task(self._task_loop(*args, **kwargs))
        except RuntimeError:
            get_logger().error(
                "Unable to start task without a running event loop! We recommend starting tasks within an `on_startup` event."
            )

    def stop(self) -> None:
        """End this task."""
        self._stop.set()
        if self.task:
            self.task.cancel()

    def restart(self, *args, **kwargs) -> None:
        """Restart this task."""
        self.stop()
        self.start(*args, **kwargs)

    def reschedule(self, trigger: BaseTrigger) -> None:
        """
        Change the trigger being used by this task.

        Args:
            trigger: The new Trigger to use

        """
        self.trigger = trigger
        self.restart()

    @classmethod
    def create(cls, trigger: BaseTrigger) -> Callable[[Callable], "Task"]:
        """
        A decorator to create a task.

        Example:
            ```python
            @Task.create(IntervalTrigger(minutes=5))
            async def my_task():
                print("It's been 5 minutes!")

            @listen()
            async def on_startup():
                my_task.start()
            ```

        Args:
            trigger: The trigger to use for this task

        """

        def wrapper(func: Callable) -> "Task":
            return cls(func, trigger)

        return wrapper

delta_until_run: timedelta | None property

Get the time until the next run of this task.

done: bool property

Whether the task is done/finished

next_run: datetime | None property

Get the next datetime this task will run.

running: bool property

Whether the task is running

started: bool property

Whether the task is started

create(trigger) classmethod

A decorator to create a task.

Example
1
2
3
4
5
6
7
@Task.create(IntervalTrigger(minutes=5))
async def my_task():
    print("It's been 5 minutes!")

@listen()
async def on_startup():
    my_task.start()

Parameters:

Name Type Description Default
trigger BaseTrigger

The trigger to use for this task

required
Source code in interactions/models/internal/tasks/task.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
@classmethod
def create(cls, trigger: BaseTrigger) -> Callable[[Callable], "Task"]:
    """
    A decorator to create a task.

    Example:
        ```python
        @Task.create(IntervalTrigger(minutes=5))
        async def my_task():
            print("It's been 5 minutes!")

        @listen()
        async def on_startup():
            my_task.start()
        ```

    Args:
        trigger: The trigger to use for this task

    """

    def wrapper(func: Callable) -> "Task":
        return cls(func, trigger)

    return wrapper

on_error(error)

Error handler for this task. Called when an exception is raised during execution of the task.

Source code in interactions/models/internal/tasks/task.py
73
74
75
76
def on_error(self, error: Exception) -> None:
    """Error handler for this task. Called when an exception is raised during execution of the task."""
    self.on_error_sentry_hook(error)
    interactions.Client.default_error_handler("Task", error)

on_error_sentry_hook(error)

A dummy method for interactions.ext.sentry to hook

Source code in interactions/models/internal/tasks/task.py
70
71
def on_error_sentry_hook(self, error: Exception) -> None:
    """A dummy method for interactions.ext.sentry to hook"""

reschedule(trigger)

Change the trigger being used by this task.

Parameters:

Name Type Description Default
trigger BaseTrigger

The new Trigger to use

required
Source code in interactions/models/internal/tasks/task.py
134
135
136
137
138
139
140
141
142
143
def reschedule(self, trigger: BaseTrigger) -> None:
    """
    Change the trigger being used by this task.

    Args:
        trigger: The new Trigger to use

    """
    self.trigger = trigger
    self.restart()

restart(*args, **kwargs)

Restart this task.

Source code in interactions/models/internal/tasks/task.py
129
130
131
132
def restart(self, *args, **kwargs) -> None:
    """Restart this task."""
    self.stop()
    self.start(*args, **kwargs)

start(*args, **kwargs)

Start this task.

Source code in interactions/models/internal/tasks/task.py
112
113
114
115
116
117
118
119
120
121
def start(self, *args, **kwargs) -> None:
    """Start this task."""
    try:
        self.trigger.reschedule()
        self._stop.clear()
        self.task = asyncio.create_task(self._task_loop(*args, **kwargs))
    except RuntimeError:
        get_logger().error(
            "Unable to start task without a running event loop! We recommend starting tasks within an `on_startup` event."
        )

stop()

End this task.

Source code in interactions/models/internal/tasks/task.py
123
124
125
126
127
def stop(self) -> None:
    """End this task."""
    self._stop.set()
    if self.task:
        self.task.cancel()

TimeTrigger

Bases: BaseTrigger

Trigger the task every day, at a specified (24 hour clock) time.

Attributes:

Name Type Description
hour int

The hour of the day (24 hour clock)

minute int

The minute of the hour

seconds int

The seconds of the minute

utc bool

If this time is in UTC

Source code in interactions/models/internal/tasks/triggers.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
class TimeTrigger(BaseTrigger):
    """
    Trigger the task every day, at a specified (24 hour clock) time.

    Attributes:
        hour int: The hour of the day (24 hour clock)
        minute int: The minute of the hour
        seconds int: The seconds of the minute
        utc bool: If this time is in UTC

    """

    def __init__(self, hour: int = 0, minute: int = 0, seconds: int = 0, utc: bool = True) -> None:
        self.target_time = (hour, minute, seconds)
        self.tz = timezone.utc if utc else None

    def next_fire(self) -> datetime | None:
        now = datetime.now()
        target = datetime(
            now.year,
            now.month,
            now.day,
            self.target_time[0],
            self.target_time[1],
            self.target_time[2],
            tzinfo=self.tz,
        )
        if target.tzinfo == timezone.utc:
            target = target.astimezone(now.tzinfo)
            # target can fall behind or go forward a day, but all we need is the time itself
            # to be converted
            # to ensure it's on the same day as "now" and not break the next if statement,
            # we can just replace the date with now's date
            target = target.replace(year=now.year, month=now.month, day=now.day, tzinfo=None)

        if target <= self.last_call_time:
            target += timedelta(days=1)
        return target

Wait

Class for waiting for a future event to happen. Internally used by wait_for.

Source code in interactions/models/internal/wait.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Wait:
    """Class for waiting for a future event to happen. Internally used by wait_for."""

    def __init__(
        self, event: str, checks: Optional[Union[Callable[..., bool], Callable[..., Awaitable[bool]]]], future: Future
    ) -> None:
        self.event: str = event
        self.check: Optional[Union[Callable[..., bool], Callable[..., Awaitable[bool]]]] = checks
        self.future: Future = future

    async def __call__(self, *args, **kwargs) -> bool:
        if self.future.cancelled():
            return True

        if self.check:
            try:
                if iscoroutinefunction(self.check):
                    check_result = await self.check(*args, **kwargs)
                else:
                    check_result = self.check(*args, **kwargs)
            except Exception as exc:
                self.future.set_exception(exc)
                return True
        else:
            check_result = True

        if check_result:
            self.future.set_result(*args, **kwargs)
            return True

        return False

AsyncIterator

Bases: _AsyncIterator, ABC

Source code in interactions/models/misc/iterator.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
class AsyncIterator(_AsyncIterator, ABC):
    def __init__(self, limit: int = 50) -> None:
        self._queue: asyncio.Queue = asyncio.Queue()
        """The queue of items in the iterator"""

        self._limit: int = limit or MISSING
        """the limit of items to retrieve"""

        self.last: Absent[Any] = MISSING
        """The last item retrieved"""

        self._retrieved_objects: List = []
        """All items this iterator has retrieved"""

    @property
    def _continue(self) -> bool:
        """Whether iteration should continue. Returns False if the limit has been reached."""
        return len(self._retrieved_objects) < self._limit if self._limit else True

    @property
    def get_limit(self) -> int:
        """Get how the maximum number of items that should be retrieved."""
        return min(self._limit - len(self._retrieved_objects), 100) if self._limit else 100

    @property
    def total_retrieved(self) -> int:
        """Get the total number of objects this iterator has retrieved."""
        return len(self._retrieved_objects)

    async def add_object(self, obj) -> None:
        """Add an object to iterator's queue."""
        return await self._queue.put(obj)

    @abstractmethod
    async def fetch(self) -> list:
        """
        Fetch additional objects.

        Your implementation of this method *must* return a list of objects.
        If no more objects are available, raise QueueEmpty

        Returns:
            List of objects

        Raises:
            QueueEmpty:  when no more objects are available.

        """
        ...

    async def _get_items(self) -> None:
        if self._continue:
            data = await self.fetch()
            [await self.add_object(obj) for obj in data]
        else:
            raise QueueEmpty

    async def __anext__(self) -> Any:
        try:
            if self._queue.empty():
                await self._get_items()
            self.last = self._queue.get_nowait()

            # add the message to the already retrieved objects, so that the search function works when calling it multiple times
            self._retrieved_objects.append(self.last)

            return self.last
        except QueueEmpty as e:
            raise StopAsyncIteration from e

    async def flatten(self) -> List:
        """Flatten this iterator into a list of objects."""
        return [elem async for elem in self]

    async def search(self, target_id: "snowflake.Snowflake_Type") -> bool:
        """Search the iterator for an object with the given ID."""
        target_id = snowflake.to_snowflake(target_id)

        if target_id in [o.id for o in self._retrieved_objects]:
            return True

        async for o in self:
            if o.id == target_id:
                return True
        return False

get_limit: int property

Get how the maximum number of items that should be retrieved.

last: Absent[Any] = MISSING instance-attribute

The last item retrieved

total_retrieved: int property

Get the total number of objects this iterator has retrieved.

add_object(obj) async

Add an object to iterator's queue.

Source code in interactions/models/misc/iterator.py
42
43
44
async def add_object(self, obj) -> None:
    """Add an object to iterator's queue."""
    return await self._queue.put(obj)

fetch() abstractmethod async

Fetch additional objects.

Your implementation of this method must return a list of objects. If no more objects are available, raise QueueEmpty

Returns:

Type Description
list

List of objects

Raises:

Type Description
QueueEmpty

when no more objects are available.

Source code in interactions/models/misc/iterator.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@abstractmethod
async def fetch(self) -> list:
    """
    Fetch additional objects.

    Your implementation of this method *must* return a list of objects.
    If no more objects are available, raise QueueEmpty

    Returns:
        List of objects

    Raises:
        QueueEmpty:  when no more objects are available.

    """
    ...

flatten() async

Flatten this iterator into a list of objects.

Source code in interactions/models/misc/iterator.py
83
84
85
async def flatten(self) -> List:
    """Flatten this iterator into a list of objects."""
    return [elem async for elem in self]

search(target_id) async

Search the iterator for an object with the given ID.

Source code in interactions/models/misc/iterator.py
87
88
89
90
91
92
93
94
95
96
97
async def search(self, target_id: "snowflake.Snowflake_Type") -> bool:
    """Search the iterator for an object with the given ID."""
    target_id = snowflake.to_snowflake(target_id)

    if target_id in [o.id for o in self._retrieved_objects]:
        return True

    async for o in self:
        if o.id == target_id:
            return True
    return False

API

ConnectionState

Source code in interactions/api/gateway/state.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ConnectionState:
    client: "Client"
    """The bot's client"""
    intents: Intents
    """The event intents in use"""
    shard_id: int
    """The shard ID of this state"""
    _shard_ready: asyncio.Event = attrs.field(repr=False, default=None)
    """Indicates that this state is now ready"""

    gateway: Absent[GatewayClient] = MISSING
    """The websocket connection for the Discord Gateway."""

    start_time: Absent[datetime] = MISSING
    """The DateTime the bot started at"""

    gateway_url: str = MISSING
    """The URL that the gateway should connect to."""

    gateway_started: asyncio.Event = asyncio.Event()
    """Event to check if the gateway has been started."""

    _shard_task: asyncio.Task | None = None

    logger: Logger = attrs.field(repr=False, init=False, factory=get_logger)

    def __attrs_post_init__(self, *args, **kwargs) -> None:
        self._shard_ready = asyncio.Event()

    @property
    def latency(self) -> float:
        """Returns the latency of the websocket connection (seconds)."""
        return self.gateway.latency

    @property
    def average_latency(self) -> float:
        """Returns the average latency of the websocket connection (seconds)."""
        return self.gateway.average_latency

    @property
    def presence(self) -> dict:
        """Returns the presence of the bot."""
        return {
            "status": self.client._status,
            "activities": [self.client._activity.to_dict()] if self.client._activity else [],
        }

    async def start(self) -> None:
        """Connect to the Discord Gateway."""
        self.gateway_url = await self.client.http.get_gateway()

        self.wrapped_logger(logging.INFO, "Starting Shard")
        self.start_time = datetime.now()
        self._shard_task = asyncio.create_task(self._ws_connect())

        self.gateway_started.set()

        # Historically this method didn't return until the connection closed
        # so we need to wait for the task to exit.
        await self._shard_task

    async def stop(self) -> None:
        """Disconnect from the Discord Gateway."""
        self.wrapped_logger(logging.INFO, "Stopping Shard")
        if self.gateway is not None:
            self.gateway.close()
            self.gateway = None

        if self._shard_task is not None:
            await self._shard_task
            self._shard_task = None

        self.gateway_started.clear()

    def clear_ready(self) -> None:
        """Clear the ready event."""
        self._shard_ready.clear()
        self.client._ready.clear()  # noinspection PyProtectedMember

    async def _ws_connect(self) -> None:
        """Connect to the Discord Gateway."""
        self.wrapped_logger(logging.INFO, "Shard is attempting to connect to gateway...")
        try:
            async with GatewayClient(self, (self.shard_id, self.client.total_shards)) as self.gateway:
                try:
                    await self.gateway.run()
                finally:
                    self._shard_ready.clear()
                    if self.client.total_shards == 1:
                        self.client.dispatch(events.Disconnect())
                    else:
                        self.client.dispatch(events.ShardDisconnect(self.shard_id))

        except WebSocketClosed as ex:
            if ex.code == 4011:
                raise LibraryException("Your bot is too large, you must use shards") from None
            if ex.code == 4013:
                raise LibraryException(f"Invalid Intents have been passed: {self.intents}") from None
            if ex.code == 4014:
                raise LibraryException(
                    "You have requested privileged intents that have not been enabled or approved. Check the developer dashboard"
                ) from None
            raise

        except Exception as e:
            self.client.dispatch(events.Disconnect())
            self.wrapped_logger(logging.ERROR, "".join(traceback.format_exception(type(e), e, e.__traceback__)))

    def wrapped_logger(self, level: int, message: str, **kwargs) -> None:
        """
        A logging wrapper that adds shard information to the message.

        Args:
            level: The logging level
            message: The message to log
            **kwargs: Any additional keyword arguments that Logger.log accepts

        """
        self.logger.log(level, f"Shard ID {self.shard_id} | {message}", **kwargs)

    async def change_presence(
        self,
        status: Optional[Union[str, Status]] = Status.ONLINE,
        activity: Absent[Union[Activity, str]] = MISSING,
    ) -> None:
        """
        Change the bots presence.

        Args:
            status: The status for the bot to be. i.e. online, afk, etc.
            activity: The activity for the bot to be displayed as doing.

        !!! note
            Bots may only be `playing` `streaming` `listening` `watching` or `competing`, other activity types are likely to fail.

        """
        if activity is MISSING:
            activity = self.client.activity

        elif activity is None:
            activity = []
        else:
            if not isinstance(activity, Activity):
                # squash whatever the user passed into an activity
                activity = Activity.create(name=str(activity))

            if activity.type == ActivityType.STREAMING:
                if not activity.url:
                    self.wrapped_logger(
                        logging.WARNING, "Streaming activity cannot be set without a valid URL attribute"
                    )
            elif activity.type not in [
                ActivityType.GAME,
                ActivityType.STREAMING,
                ActivityType.LISTENING,
                ActivityType.WATCHING,
                ActivityType.COMPETING,
                ActivityType.CUSTOM,
            ]:
                self.wrapped_logger(
                    logging.WARNING, f"Activity type `{ActivityType(activity.type).name}` may not be enabled for bots"
                )
        if status:
            if not isinstance(status, Status):
                try:
                    status = Status[status.upper()]
                except KeyError:
                    raise ValueError(f"`{status}` is not a valid status type. Please use the Status enum") from None
        elif self.client.status:
            status = self.client.status
        else:
            self.wrapped_logger(logging.WARNING, "Status must be set to a valid status type, defaulting to online")
            status = Status.ONLINE

        self.client._status = status
        self.client._activity = activity
        await self.gateway.change_presence(activity.to_dict() if activity else None, status)

    def get_voice_state(self, guild_id: "Snowflake_Type") -> Optional["interactions.ActiveVoiceState"]:
        """
        Get the bot's voice state for a guild.

        Args:
            guild_id: The target guild's id.

        Returns:
            The bot's voice state for the guild if connected, otherwise None.

        """
        return self.client.cache.get_bot_voice_state(guild_id)

    async def voice_connect(
        self,
        guild_id: "Snowflake_Type",
        channel_id: "Snowflake_Type",
        muted: bool = False,
        deafened: bool = False,
    ) -> "interactions.ActiveVoiceState":
        """
        Connect to a voice channel.

        Args:
            guild_id: id of the guild the voice channel is in.
            channel_id: id of the voice channel client wants to join.
            muted: Whether the bot should be muted when connected.
            deafened: Whether the bot should be deafened when connected.

        Returns:
            The new active voice state on successfully connection.

        """
        voice_state = interactions.ActiveVoiceState(
            client=self.client,
            guild_id=guild_id,
            channel_id=channel_id,
            self_mute=muted,
            self_deaf=deafened,
        )
        await voice_state.connect()
        self.client.cache.place_bot_voice_state(voice_state)
        return voice_state

average_latency: float property

Returns the average latency of the websocket connection (seconds).

client: Client class-attribute

The bot's client

gateway: Absent[GatewayClient] = MISSING class-attribute

The websocket connection for the Discord Gateway.

gateway_started: asyncio.Event = asyncio.Event() class-attribute

Event to check if the gateway has been started.

gateway_url: str = MISSING class-attribute

The URL that the gateway should connect to.

intents: Intents class-attribute

The event intents in use

latency: float property

Returns the latency of the websocket connection (seconds).

presence: dict property

Returns the presence of the bot.

shard_id: int class-attribute

The shard ID of this state

start_time: Absent[datetime] = MISSING class-attribute

The DateTime the bot started at

change_presence(status=Status.ONLINE, activity=MISSING) async

Change the bots presence.

Parameters:

Name Type Description Default
status Optional[Union[str, Status]]

The status for the bot to be. i.e. online, afk, etc.

Status.ONLINE
activity Absent[Union[Activity, str]]

The activity for the bot to be displayed as doing.

MISSING

Note

Bots may only be playing streaming listening watching or competing, other activity types are likely to fail.

Source code in interactions/api/gateway/state.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
async def change_presence(
    self,
    status: Optional[Union[str, Status]] = Status.ONLINE,
    activity: Absent[Union[Activity, str]] = MISSING,
) -> None:
    """
    Change the bots presence.

    Args:
        status: The status for the bot to be. i.e. online, afk, etc.
        activity: The activity for the bot to be displayed as doing.

    !!! note
        Bots may only be `playing` `streaming` `listening` `watching` or `competing`, other activity types are likely to fail.

    """
    if activity is MISSING:
        activity = self.client.activity

    elif activity is None:
        activity = []
    else:
        if not isinstance(activity, Activity):
            # squash whatever the user passed into an activity
            activity = Activity.create(name=str(activity))

        if activity.type == ActivityType.STREAMING:
            if not activity.url:
                self.wrapped_logger(
                    logging.WARNING, "Streaming activity cannot be set without a valid URL attribute"
                )
        elif activity.type not in [
            ActivityType.GAME,
            ActivityType.STREAMING,
            ActivityType.LISTENING,
            ActivityType.WATCHING,
            ActivityType.COMPETING,
            ActivityType.CUSTOM,
        ]:
            self.wrapped_logger(
                logging.WARNING, f"Activity type `{ActivityType(activity.type).name}` may not be enabled for bots"
            )
    if status:
        if not isinstance(status, Status):
            try:
                status = Status[status.upper()]
            except KeyError:
                raise ValueError(f"`{status}` is not a valid status type. Please use the Status enum") from None
    elif self.client.status:
        status = self.client.status
    else:
        self.wrapped_logger(logging.WARNING, "Status must be set to a valid status type, defaulting to online")
        status = Status.ONLINE

    self.client._status = status
    self.client._activity = activity
    await self.gateway.change_presence(activity.to_dict() if activity else None, status)

clear_ready()

Clear the ready event.

Source code in interactions/api/gateway/state.py
 99
100
101
102
def clear_ready(self) -> None:
    """Clear the ready event."""
    self._shard_ready.clear()
    self.client._ready.clear()  # noinspection PyProtectedMember

get_voice_state(guild_id)

Get the bot's voice state for a guild.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The target guild's id.

required

Returns:

Type Description
Optional[ActiveVoiceState]

The bot's voice state for the guild if connected, otherwise None.

Source code in interactions/api/gateway/state.py
203
204
205
206
207
208
209
210
211
212
213
214
def get_voice_state(self, guild_id: "Snowflake_Type") -> Optional["interactions.ActiveVoiceState"]:
    """
    Get the bot's voice state for a guild.

    Args:
        guild_id: The target guild's id.

    Returns:
        The bot's voice state for the guild if connected, otherwise None.

    """
    return self.client.cache.get_bot_voice_state(guild_id)

start() async

Connect to the Discord Gateway.

Source code in interactions/api/gateway/state.py
72
73
74
75
76
77
78
79
80
81
82
83
84
async def start(self) -> None:
    """Connect to the Discord Gateway."""
    self.gateway_url = await self.client.http.get_gateway()

    self.wrapped_logger(logging.INFO, "Starting Shard")
    self.start_time = datetime.now()
    self._shard_task = asyncio.create_task(self._ws_connect())

    self.gateway_started.set()

    # Historically this method didn't return until the connection closed
    # so we need to wait for the task to exit.
    await self._shard_task

stop() async

Disconnect from the Discord Gateway.

Source code in interactions/api/gateway/state.py
86
87
88
89
90
91
92
93
94
95
96
97
async def stop(self) -> None:
    """Disconnect from the Discord Gateway."""
    self.wrapped_logger(logging.INFO, "Stopping Shard")
    if self.gateway is not None:
        self.gateway.close()
        self.gateway = None

    if self._shard_task is not None:
        await self._shard_task
        self._shard_task = None

    self.gateway_started.clear()

voice_connect(guild_id, channel_id, muted=False, deafened=False) async

Connect to a voice channel.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

id of the guild the voice channel is in.

required
channel_id Snowflake_Type

id of the voice channel client wants to join.

required
muted bool

Whether the bot should be muted when connected.

False
deafened bool

Whether the bot should be deafened when connected.

False

Returns:

Type Description
ActiveVoiceState

The new active voice state on successfully connection.

Source code in interactions/api/gateway/state.py
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
async def voice_connect(
    self,
    guild_id: "Snowflake_Type",
    channel_id: "Snowflake_Type",
    muted: bool = False,
    deafened: bool = False,
) -> "interactions.ActiveVoiceState":
    """
    Connect to a voice channel.

    Args:
        guild_id: id of the guild the voice channel is in.
        channel_id: id of the voice channel client wants to join.
        muted: Whether the bot should be muted when connected.
        deafened: Whether the bot should be deafened when connected.

    Returns:
        The new active voice state on successfully connection.

    """
    voice_state = interactions.ActiveVoiceState(
        client=self.client,
        guild_id=guild_id,
        channel_id=channel_id,
        self_mute=muted,
        self_deaf=deafened,
    )
    await voice_state.connect()
    self.client.cache.place_bot_voice_state(voice_state)
    return voice_state

wrapped_logger(level, message, **kwargs)

A logging wrapper that adds shard information to the message.

Parameters:

Name Type Description Default
level int

The logging level

required
message str

The message to log

required
**kwargs

Any additional keyword arguments that Logger.log accepts

{}
Source code in interactions/api/gateway/state.py
133
134
135
136
137
138
139
140
141
142
143
def wrapped_logger(self, level: int, message: str, **kwargs) -> None:
    """
    A logging wrapper that adds shard information to the message.

    Args:
        level: The logging level
        message: The message to log
        **kwargs: Any additional keyword arguments that Logger.log accepts

    """
    self.logger.log(level, f"Shard ID {self.shard_id} | {message}", **kwargs)

Outlines the interaction between interactions and Discord's Gateway API.

GatewayClient

Bases: WebsocketClient

Abstraction over one gateway connection.

Multiple WebsocketClient instances can be used to implement same-process sharding.

Attributes:

Name Type Description
sequence

The sequence of this connection

session_id

The session ID of this connection

Source code in interactions/api/gateway/gateway.py
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
class GatewayClient(WebsocketClient):
    """
    Abstraction over one gateway connection.

    Multiple `WebsocketClient` instances can be used to implement same-process sharding.

    Attributes:
        sequence: The sequence of this connection
        session_id: The session ID of this connection

    """

    def __init__(self, state: "ConnectionState", shard: tuple[int, int]) -> None:
        super().__init__(state)

        self.shard = shard

        self.chunk_cache = {}

        self._trace = []
        self.sequence = None
        self.session_id = None

        self.ws_url = state.gateway_url
        self.ws_resume_url = MISSING

        # This lock needs to be held to send something over the gateway, but is also held when
        # reconnecting. That way there's no race conditions between sending and reconnecting.
        self._race_lock = asyncio.Lock()
        # Then this event is used so that receive() can wait for the reconnecting to complete.
        self._closed = asyncio.Event()

        self._keep_alive = None
        self._kill_bee_gees = asyncio.Event()
        self._last_heartbeat = 0
        self._acknowledged = asyncio.Event()
        self._acknowledged.set()  # Initialize it as set

        self._ready = asyncio.Event()
        self._close_gateway = asyncio.Event()

        # Sanity check, it is extremely important that an instance isn't reused.
        self._entered = False

    async def __aenter__(self: SELF) -> SELF:
        if self._entered:
            raise RuntimeError("An instance of 'WebsocketClient' cannot be re-used!")

        self._entered = True
        self._zlib = zlib.decompressobj()

        self.ws = await self.state.client.http.websocket_connect(self.state.gateway_url)

        hello = await self.receive(force=True)
        self.heartbeat_interval = hello["d"]["heartbeat_interval"] / 1000
        self._closed.set()

        self._keep_alive = asyncio.create_task(self.run_bee_gees())

        await self._identify()

        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        traceback: TracebackType | None,
    ) -> None:
        # Technically should not be possible in any way, but might as well be safe worst-case.
        self._close_gateway.set()

        try:
            if self._keep_alive is not None:
                self._kill_bee_gees.set()
                try:
                    # Even if we get cancelled that is fine, because then the keep-alive
                    # handler will also be cancelled since we're waiting on it.
                    await self._keep_alive  # Wait for the keep-alive handler to finish
                finally:
                    self._keep_alive = None
        finally:
            if self.ws is not None:
                # We could be cancelled here, it is extremely important that we close the
                # WebSocket either way, hence the try/except.
                try:
                    await self.ws.close(code=1000)
                finally:
                    self.ws = None

    async def run(self) -> None:
        """Start receiving events from the websocket."""
        while True:
            if self._stopping is None:
                self._stopping = asyncio.create_task(self._close_gateway.wait())
            receiving = asyncio.create_task(self.receive())
            done, _ = await asyncio.wait({self._stopping, receiving}, return_when=asyncio.FIRST_COMPLETED)

            if receiving in done:
                # Note that we check for a received message first, because if both completed at
                # the same time, we don't want to discard that message.
                msg = await receiving
            else:
                # This has to be the stopping task, which we join into the current task (even
                # though that doesn't give any meaningful value in the return).
                await self._stopping
                receiving.cancel()
                return

            op = msg.get("op")
            data = msg.get("d")
            seq = msg.get("s")
            event = msg.get("t")

            if seq:
                self.sequence = seq

            if op == OPCODE.DISPATCH:
                _ = asyncio.create_task(self.dispatch_event(data, seq, event))  # noqa: RUF006
                continue

            # This may try to reconnect the connection so it is best to wait
            # for it to complete before receiving more - that way there's less
            # possible race conditions to consider.
            await self.dispatch_opcode(data, op)

    async def dispatch_opcode(self, data, op: OPCODE) -> None:
        match op:
            case OPCODE.HEARTBEAT:
                self.state.wrapped_logger(logging.DEBUG, "❤ Received heartbeat request from gateway")
                return await self.send_heartbeat()

            case OPCODE.HEARTBEAT_ACK:
                self._latency.append(time.perf_counter() - self._last_heartbeat)

                if self._last_heartbeat != 0 and self._latency[-1] >= 15:
                    self.state.wrapped_logger(
                        logging.WARNING,
                        f"❤ High Latency! shard ID {self.shard[0]} heartbeat took {self._latency[-1]:.1f}s to be acknowledged!",
                    )
                else:
                    self.state.wrapped_logger(logging.DEBUG, "❤ Received heartbeat acknowledgement from gateway")

                return self._acknowledged.set()

            case OPCODE.RECONNECT:
                self.state.wrapped_logger(logging.DEBUG, "Gateway requested reconnect. Reconnecting...")
                return await self.reconnect(resume=True, url=self.ws_resume_url)

            case OPCODE.INVALIDATE_SESSION:
                self.state.wrapped_logger(logging.WARNING, "Gateway invalidated session. Reconnecting...")
                return await self.reconnect()

            case _:
                return self.state.wrapped_logger(logging.DEBUG, f"Unhandled OPCODE: {op} = {OPCODE(op).name}")

    async def dispatch_event(self, data, seq, event) -> None:
        match event:
            case "READY":
                self._ready.set()
                self._trace = data.get("_trace", [])
                self.sequence = seq
                self.session_id = data["session_id"]
                self.ws_resume_url = (
                    f"{data['resume_gateway_url']}?encoding=json&v={__api_version__}&compress=zlib-stream"
                )
                self.state.wrapped_logger(logging.INFO, "Gateway connection established")
                self.state.wrapped_logger(logging.DEBUG, f"Session ID: {self.session_id} Trace: {self._trace}")
                return self.state.client.dispatch(events.WebsocketReady(data))

            case "RESUMED":
                self.state._shard_ready.set()
                self.state.wrapped_logger(
                    logging.INFO, f"Successfully resumed connection! Session_ID: {self.session_id}"
                )
                self.state.client.dispatch(events.Resume())
                return None

            case "GUILD_MEMBERS_CHUNK":
                _ = asyncio.create_task(self._process_member_chunk(data.copy()))

            case _:
                # the above events are "special", and are handled by the gateway itself, the rest can be dispatched
                event_name = f"raw_{event.lower()}"
                if processor := self.state.client.processors.get(event_name):
                    try:
                        _ = asyncio.create_task(  # noqa: RUF006
                            processor(events.RawGatewayEvent(data.copy(), override_name=event_name))
                        )
                    except Exception as ex:
                        self.state.wrapped_logger(
                            logging.ERROR, f"Failed to run event processor for {event_name}: {ex}"
                        )
                else:
                    self.state.wrapped_logger(logging.DEBUG, f"No processor for `{event_name}`")

        self.state.client.dispatch(events.RawGatewayEvent(data.copy(), override_name="raw_gateway_event"))
        self.state.client.dispatch(events.RawGatewayEvent(data.copy(), override_name=f"raw_{event.lower()}"))

    def close(self) -> None:
        """Shutdown the websocket connection."""
        self._close_gateway.set()

    async def _identify(self) -> None:
        """Send an identify payload to the gateway."""
        if self.ws is None:
            raise RuntimeError
        payload = {
            "op": OPCODE.IDENTIFY,
            "d": {
                "token": self.state.client.http.token,
                "intents": self.state.intents,
                "shard": self.shard,
                "large_threshold": 250,
                "properties": {
                    "os": sys.platform,
                    "browser": "interactions",
                    "device": "interactions",
                },
                "presence": self.state.presence,
            },
            "compress": True,
        }

        serialized = FastJson.dumps(payload)
        await self.ws.send_str(serialized)

        self.state.wrapped_logger(
            logging.DEBUG, f"Identification payload sent to gateway, requesting intents: {self.state.intents}"
        )

    async def reconnect(self, *, resume: bool = False, code: int = 1012, url: str | None = None) -> None:
        self.state.clear_ready()
        self._ready.clear()
        await super().reconnect(resume=resume, code=code, url=url)

    async def _resume_connection(self) -> None:
        """Send a resume payload to the gateway."""
        if self.ws is None:
            raise RuntimeError

        payload = {
            "op": OPCODE.RESUME,
            "d": {
                "token": self.state.client.http.token,
                "seq": self.sequence,
                "session_id": self.session_id,
            },
        }

        serialized = FastJson.dumps(payload)
        await self.ws.send_str(serialized)

        self.state.wrapped_logger(logging.DEBUG, f"Resume payload sent to gateway, session ID: {self.session_id}")

    async def send_heartbeat(self) -> None:
        await self.send_json({"op": OPCODE.HEARTBEAT, "d": self.sequence}, bypass=True)
        self.state.wrapped_logger(logging.DEBUG, "❤ Gateway is sending a Heartbeat")

    async def change_presence(self, activity=None, status: Status = Status.ONLINE, since=None) -> None:
        """Update the bot's presence status."""
        await self.send_json(
            {
                "op": OPCODE.PRESENCE,
                "d": dict_filter_none(
                    {
                        "since": int(since or time.time() * 1000),
                        "activities": [activity] if activity else [],
                        "status": status,
                        "afk": False,
                    }
                ),
            }
        )

    async def request_member_chunks(
        self,
        guild_id: "Snowflake_Type",
        query="",
        *,
        limit,
        user_ids=None,
        presences=False,
        nonce=None,
    ) -> None:
        payload = {
            "op": OPCODE.REQUEST_MEMBERS,
            "d": dict_filter_none(
                {
                    "guild_id": guild_id,
                    "presences": presences,
                    "limit": limit,
                    "nonce": nonce,
                    "user_ids": user_ids,
                    "query": query,
                }
            ),
        }
        await self.send_json(payload)

    async def _process_member_chunk(self, chunk: dict) -> Task[None]:
        if guild := self.state.client.cache.get_guild(to_snowflake(chunk.get("guild_id"))):
            return asyncio.create_task(guild.process_member_chunk(chunk))
        raise ValueError(f"No guild exists for {chunk.get('guild_id')}")

    async def voice_state_update(
        self,
        guild_id: "Snowflake_Type",
        channel_id: "Snowflake_Type",
        muted: bool = False,
        deafened: bool = False,
    ) -> None:
        """Update the bot's voice state."""
        payload = {
            "op": OPCODE.VOICE_STATE,
            "d": {
                "guild_id": guild_id,
                "channel_id": channel_id,
                "self_mute": muted,
                "self_deaf": deafened,
            },
        }
        await self.send_json(payload)

change_presence(activity=None, status=Status.ONLINE, since=None) async

Update the bot's presence status.

Source code in interactions/api/gateway/gateway.py
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
async def change_presence(self, activity=None, status: Status = Status.ONLINE, since=None) -> None:
    """Update the bot's presence status."""
    await self.send_json(
        {
            "op": OPCODE.PRESENCE,
            "d": dict_filter_none(
                {
                    "since": int(since or time.time() * 1000),
                    "activities": [activity] if activity else [],
                    "status": status,
                    "afk": False,
                }
            ),
        }
    )

close()

Shutdown the websocket connection.

Source code in interactions/api/gateway/gateway.py
246
247
248
def close(self) -> None:
    """Shutdown the websocket connection."""
    self._close_gateway.set()

run() async

Start receiving events from the websocket.

Source code in interactions/api/gateway/gateway.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
async def run(self) -> None:
    """Start receiving events from the websocket."""
    while True:
        if self._stopping is None:
            self._stopping = asyncio.create_task(self._close_gateway.wait())
        receiving = asyncio.create_task(self.receive())
        done, _ = await asyncio.wait({self._stopping, receiving}, return_when=asyncio.FIRST_COMPLETED)

        if receiving in done:
            # Note that we check for a received message first, because if both completed at
            # the same time, we don't want to discard that message.
            msg = await receiving
        else:
            # This has to be the stopping task, which we join into the current task (even
            # though that doesn't give any meaningful value in the return).
            await self._stopping
            receiving.cancel()
            return

        op = msg.get("op")
        data = msg.get("d")
        seq = msg.get("s")
        event = msg.get("t")

        if seq:
            self.sequence = seq

        if op == OPCODE.DISPATCH:
            _ = asyncio.create_task(self.dispatch_event(data, seq, event))  # noqa: RUF006
            continue

        # This may try to reconnect the connection so it is best to wait
        # for it to complete before receiving more - that way there's less
        # possible race conditions to consider.
        await self.dispatch_opcode(data, op)

voice_state_update(guild_id, channel_id, muted=False, deafened=False) async

Update the bot's voice state.

Source code in interactions/api/gateway/gateway.py
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
async def voice_state_update(
    self,
    guild_id: "Snowflake_Type",
    channel_id: "Snowflake_Type",
    muted: bool = False,
    deafened: bool = False,
) -> None:
    """Update the bot's voice state."""
    payload = {
        "op": OPCODE.VOICE_STATE,
        "d": {
            "guild_id": guild_id,
            "channel_id": channel_id,
            "self_mute": muted,
            "self_deaf": deafened,
        },
    }
    await self.send_json(payload)

VoiceGateway

Bases: WebsocketClient

Source code in interactions/api/voice/voice_gateway.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
class VoiceGateway(WebsocketClient):
    guild_id: str
    heartbeat_interval: int
    session_id: str
    token: str
    encryptor: Encryption

    ssrc: int
    me_ip: str
    me_port: int
    voice_ip: str
    voice_port: int
    voice_modes: list[str]
    selected_mode: str
    socket: socket.socket
    ready: Event

    def __init__(self, state, voice_state: dict, voice_server: dict) -> None:
        super().__init__(state)

        self._voice_server_update = asyncio.Event()
        self.ws_url = f"wss://{voice_server['endpoint']}?v=4"
        self.session_id = voice_state["session_id"]
        self.token = voice_server["token"]
        self.secret: str | None = None
        self.guild_id = voice_server["guild_id"]

        self.sock_sequence = 0
        self.timestamp = 0
        self.ready = Event()
        self.user_ssrc_map = {}
        self.cond = None

        self._udp_ka = threading.Thread(target=self._udp_keep_alive, daemon=True)

    async def wait_until_ready(self) -> None:
        await asyncio.to_thread(self.ready.wait)

    async def run(self) -> None:
        """Start receiving events from the websocket."""
        while True:
            if self._stopping is None:
                self._stopping = asyncio.create_task(self._close_gateway.wait())
            receiving = asyncio.create_task(self.receive())
            done, _ = await asyncio.wait({self._stopping, receiving}, return_when=asyncio.FIRST_COMPLETED)

            if receiving in done:
                # Note that we check for a received message first, because if both completed at
                # the same time, we don't want to discard that message.
                msg = await receiving
            else:
                # This has to be the stopping task, which we join into the current task (even
                # though that doesn't give any meaningful value in the return).
                await self._stopping
                receiving.cancel()
                return

            op = msg.get("op")
            data = msg.get("d")
            if seq := msg.get("s"):
                self.sequence = seq

            # This may try to reconnect the connection so it is best to wait
            # for it to complete before receiving more - that way there's less
            # possible race conditions to consider.
            await self.dispatch_opcode(data, op)

    async def receive(self, force=False) -> str:  # noqa: C901
        buffer = bytearray()

        while True:
            if not force:
                await self._closed.wait()

            resp = await self.ws.receive()

            if resp.type == WSMsgType.CLOSE:
                self.logger.debug(f"Disconnecting from voice gateway! Reason: {resp.data}::{resp.extra}")
                if resp.data in (4006, 4009, 4014, 4015):
                    # these are all recoverable close codes, anything else means we're foobared
                    # codes: session expired, session timeout, disconnected, server crash
                    self.ready.clear()
                    # docs state only resume on 4015
                    await self.reconnect(resume=resp.data == 4015)
                    continue
                raise VoiceWebSocketClosed(resp.data)

            if resp.type is WSMsgType.CLOSED:
                if force:
                    raise RuntimeError("Discord unexpectedly closed the underlying socket during force receive!")

                if not self._closed.is_set():
                    # Because we are waiting for the even before we receive, this shouldn't be
                    # possible - the CLOSING message should be returned instead. Either way, if this
                    # is possible after all we can just wait for the event to be set.
                    await self._closed.wait()
                else:
                    # This is an odd corner-case where the underlying socket connection was closed
                    # unexpectedly without communicating the WebSocket closing handshake. We'll have
                    # to reconnect ourselves.
                    await self.reconnect(resume=True)

            elif resp.type is WSMsgType.CLOSING:
                if force:
                    raise RuntimeError("WebSocket is unexpectedly closing during force receive!")

                # This happens when the keep-alive handler is reconnecting the connection even
                # though we waited for the event before hand, because it got to run while we waited
                # for data to come in. We can just wait for the event again.
                await self._closed.wait()
                continue

            if resp.data is None:
                continue

            if isinstance(resp.data, bytes):
                buffer.extend(resp.data)

                if len(resp.data) < 4 or resp.data[-4:] != b"\x00\x00\xff\xff":
                    # message isn't complete yet, wait
                    continue

                msg = self._zlib.decompress(buffer)
                msg = msg.decode("utf-8")
            else:
                msg = resp.data

            try:
                msg = FastJson.loads(msg)
            except Exception as e:
                self.logger.error(e)

            return msg

    async def dispatch_opcode(self, data, op) -> None:
        match op:
            case OP.HEARTBEAT_ACK:
                self._latency.append(time.perf_counter() - self._last_heartbeat)

                if self._last_heartbeat != 0 and self._latency[-1] >= 15:
                    self.logger.warning(
                        f"High Latency! Voice heartbeat took {self._latency[-1]:.1f}s to be acknowledged!"
                    )
                else:
                    self.logger.debug(f"❤ Heartbeat acknowledged after {self._latency[-1]:.5f} seconds")

                return self._acknowledged.set()

            case OP.READY:
                self.logger.debug("Discord send VC Ready! Establishing a socket connection...")
                self.voice_ip = data["ip"]
                self.voice_port = data["port"]
                self.ssrc = data["ssrc"]
                self.voice_modes = [mode for mode in data["modes"] if mode in Encryption.SUPPORTED]

                if not self.voice_modes:
                    self.logger.critical("NO VOICE ENCRYPTION MODES SHARED WITH GATEWAY!")

                await self.establish_voice_socket()

            case OP.SESSION_DESCRIPTION:
                self.logger.info(f"Voice connection established; using {data['mode']}")
                self.selected_mode = data["mode"]
                self.secret = data["secret_key"]
                self.encryptor = Encryption(self.secret)
                self.ready.set()
                if self.cond:
                    with self.cond:
                        self.cond.notify()
            case OP.SPEAKING:
                self.user_ssrc_map[data["ssrc"]] = {"user_id": int(data["user_id"]), "speaking": data["speaking"]}
            case OP.CLIENT_DISCONNECT:
                self.logger.debug(
                    f"User {data['user_id']} has disconnected from voice, ssrc ({self.user_ssrc_map.pop(data['user_id'], MISSING)}) invalidated"
                )

            case _:
                return self.logger.debug(f"Unhandled OPCODE: {op} = {data = }")

    async def reconnect(self, *, resume: bool = False, code: int = 1012) -> None:
        async with self._race_lock:
            self._closed.clear()

            if self.ws is not None:
                await self.ws.close(code=code)

            self.ws = None

            if not resume:
                self.logger.debug("Waiting for updated server information...")
                try:
                    await asyncio.wait_for(self._voice_server_update.wait(), timeout=5)
                except asyncio.TimeoutError:
                    self._kill_bee_gees.set()
                    self.close()
                    self.logger.debug("Terminating VoiceGateway due to disconnection")
                    return None

                self._voice_server_update.clear()

            self.ws = await self.state.client.http.websocket_connect(self.ws_url)

            try:
                hello = await self.receive(force=True)
                self.heartbeat_interval = hello["d"]["heartbeat_interval"] / 1000
            except RuntimeError:
                # sometimes the initial connection fails with voice gateways, handle that
                return await self.reconnect(resume=resume, code=code)

            if not resume:
                await self._identify()
            else:
                await self._resume_connection()

            self._closed.set()
            self._acknowledged.set()

    async def _resume_connection(self) -> None:
        if self.ws is None:
            raise RuntimeError

        payload = {
            "op": OP.RESUME,
            "d": {"server_id": self.guild_id, "session_id": self.session_id, "token": self.token},
        }
        await self.ws.send_json(payload)

        if not self._udp_ka.is_alive():
            self._udp_ka = threading.Thread(target=self._udp_keep_alive, daemon=True)
            self._udp_ka.start()

    def _udp_keep_alive(self) -> None:
        keep_alive = b"\xc9\x00\x00\x00\x00\x00\x00\x00\x00"

        self.logger.debug("Starting UDP Keep Alive")
        while not self.socket._closed and self.ws and not self.ws.closed:
            try:
                _, writable, _ = select.select([], [self.socket], [], 0)
                while not writable:
                    _, writable, _ = select.select([], [self.socket], [], 0)

                # discord will never respond to this, but it helps maintain the hole punch
                self.socket.sendto(keep_alive, (self.voice_ip, self.voice_port))
                time.sleep(5)
            except socket.error as e:
                self.logger.warning(f"Ending Keep Alive due to {e}")
                return
            except AttributeError:
                return
            except Exception as e:
                self.logger.debug("Keep Alive Error: ", exc_info=e)
        self.logger.debug("Ending UDP Keep Alive")

    async def establish_voice_socket(self) -> None:
        """Establish the socket connection to discord"""
        self.logger.debug("IP Discovery in progress...")

        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.socket.setblocking(False)

        packet = bytearray(74)
        struct.pack_into(">H", packet, 0, 1)  # 1 = Send
        struct.pack_into(">H", packet, 2, 70)  # 70 = Length
        struct.pack_into(">I", packet, 4, self.ssrc)

        self.socket.sendto(packet, (self.voice_ip, self.voice_port))
        resp = await self.loop.sock_recv(self.socket, 74)
        self.logger.debug(f"Voice Initial Response Received: {resp}")

        ip_start = 8
        ip_end = resp.index(0, ip_start)
        self.me_ip = resp[ip_start:ip_end].decode("ascii")

        self.me_port = struct.unpack_from(">H", resp, len(resp) - 2)[0]
        self.logger.debug(f"IP Discovered: {self.me_ip} #{self.me_port}")

        await self._select_protocol()

        if not self._udp_ka.is_alive():
            self._udp_ka = threading.Thread(target=self._udp_keep_alive, daemon=True)
            self._udp_ka.start()

    def generate_packet(self, data: bytes) -> bytes:
        """Generate a packet to be sent to the voice socket."""
        header = bytearray(12)
        header[0] = 0x80
        header[1] = 0x78

        struct.pack_into(">H", header, 2, self.sock_sequence)
        struct.pack_into(">I", header, 4, self.timestamp)
        struct.pack_into(">I", header, 8, self.ssrc)

        return self.encryptor.encrypt(self.voice_modes[0], header, data)

    def send_packet(self, data: bytes, encoder, needs_encode=True) -> None:
        """Send a packet to the voice socket"""
        self.sock_sequence += 1
        if self.sock_sequence > 0xFFFF:
            self.sock_sequence = 0

        if self.timestamp > 0xFFFFFFFF:
            self.timestamp = 0

        if needs_encode:
            data = encoder.encode(data)
        packet = self.generate_packet(data)

        _, writable, _ = select.select([], [self.socket], [], 0)
        while not writable:
            _, writable, errored = select.select([], [self.socket], [], 0)
            if errored:
                self.logger.error(f"Socket errored: {errored}")
            continue
        self.socket.sendto(packet, (self.voice_ip, self.voice_port))
        self.timestamp += encoder.samples_per_frame

    async def send_heartbeat(self) -> None:
        await self.send_json({"op": OP.HEARTBEAT, "d": random.getrandbits(64)})
        self.logger.debug("❤ Voice Connection is sending Heartbeat")

    async def _identify(self) -> None:
        """Send an identify payload to the voice gateway."""
        payload = {
            "op": OP.IDENTIFY,
            "d": {
                "server_id": self.guild_id,
                "user_id": self.state.client.user.id,
                "session_id": self.session_id,
                "token": self.token,
            },
        }
        serialized = FastJson.dumps(payload)
        await self.ws.send_str(serialized)

        self.logger.debug("Voice Connection has identified itself to Voice Gateway")

    async def _select_protocol(self) -> None:
        """Inform Discord of our chosen protocol."""
        payload = {
            "op": OP.SELECT_PROTOCOL,
            "d": {
                "protocol": "udp",
                "data": {"address": self.me_ip, "port": self.me_port, "mode": self.voice_modes[0]},
            },
        }
        await self.send_json(payload)

    async def speaking(self, is_speaking: bool = True) -> None:
        """
        Tell the gateway if we're sending audio or not.

        Args:
            is_speaking: If we're sending audio or not

        """
        payload = {
            "op": OP.SPEAKING,
            "d": {
                "speaking": 1 << 0 if is_speaking else 0,
                "delay": 0,
                "ssrc": self.ssrc,
            },
        }
        await self.ws.send_json(payload)

    def set_new_voice_server(self, payload: dict) -> None:
        """
        Set a new voice server to connect to.

        Args:
            payload: New voice server connection data

        """
        self.ws_url = f"wss://{payload['endpoint']}?v=4"
        self.token = payload["token"]
        self.guild_id = payload["guild_id"]
        self._voice_server_update.set()

establish_voice_socket() async

Establish the socket connection to discord

Source code in interactions/api/voice/voice_gateway.py
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
async def establish_voice_socket(self) -> None:
    """Establish the socket connection to discord"""
    self.logger.debug("IP Discovery in progress...")

    self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    self.socket.setblocking(False)

    packet = bytearray(74)
    struct.pack_into(">H", packet, 0, 1)  # 1 = Send
    struct.pack_into(">H", packet, 2, 70)  # 70 = Length
    struct.pack_into(">I", packet, 4, self.ssrc)

    self.socket.sendto(packet, (self.voice_ip, self.voice_port))
    resp = await self.loop.sock_recv(self.socket, 74)
    self.logger.debug(f"Voice Initial Response Received: {resp}")

    ip_start = 8
    ip_end = resp.index(0, ip_start)
    self.me_ip = resp[ip_start:ip_end].decode("ascii")

    self.me_port = struct.unpack_from(">H", resp, len(resp) - 2)[0]
    self.logger.debug(f"IP Discovered: {self.me_ip} #{self.me_port}")

    await self._select_protocol()

    if not self._udp_ka.is_alive():
        self._udp_ka = threading.Thread(target=self._udp_keep_alive, daemon=True)
        self._udp_ka.start()

generate_packet(data)

Generate a packet to be sent to the voice socket.

Source code in interactions/api/voice/voice_gateway.py
318
319
320
321
322
323
324
325
326
327
328
def generate_packet(self, data: bytes) -> bytes:
    """Generate a packet to be sent to the voice socket."""
    header = bytearray(12)
    header[0] = 0x80
    header[1] = 0x78

    struct.pack_into(">H", header, 2, self.sock_sequence)
    struct.pack_into(">I", header, 4, self.timestamp)
    struct.pack_into(">I", header, 8, self.ssrc)

    return self.encryptor.encrypt(self.voice_modes[0], header, data)

run() async

Start receiving events from the websocket.

Source code in interactions/api/voice/voice_gateway.py
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
async def run(self) -> None:
    """Start receiving events from the websocket."""
    while True:
        if self._stopping is None:
            self._stopping = asyncio.create_task(self._close_gateway.wait())
        receiving = asyncio.create_task(self.receive())
        done, _ = await asyncio.wait({self._stopping, receiving}, return_when=asyncio.FIRST_COMPLETED)

        if receiving in done:
            # Note that we check for a received message first, because if both completed at
            # the same time, we don't want to discard that message.
            msg = await receiving
        else:
            # This has to be the stopping task, which we join into the current task (even
            # though that doesn't give any meaningful value in the return).
            await self._stopping
            receiving.cancel()
            return

        op = msg.get("op")
        data = msg.get("d")
        if seq := msg.get("s"):
            self.sequence = seq

        # This may try to reconnect the connection so it is best to wait
        # for it to complete before receiving more - that way there's less
        # possible race conditions to consider.
        await self.dispatch_opcode(data, op)

send_packet(data, encoder, needs_encode=True)

Send a packet to the voice socket

Source code in interactions/api/voice/voice_gateway.py
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
def send_packet(self, data: bytes, encoder, needs_encode=True) -> None:
    """Send a packet to the voice socket"""
    self.sock_sequence += 1
    if self.sock_sequence > 0xFFFF:
        self.sock_sequence = 0

    if self.timestamp > 0xFFFFFFFF:
        self.timestamp = 0

    if needs_encode:
        data = encoder.encode(data)
    packet = self.generate_packet(data)

    _, writable, _ = select.select([], [self.socket], [], 0)
    while not writable:
        _, writable, errored = select.select([], [self.socket], [], 0)
        if errored:
            self.logger.error(f"Socket errored: {errored}")
        continue
    self.socket.sendto(packet, (self.voice_ip, self.voice_port))
    self.timestamp += encoder.samples_per_frame

set_new_voice_server(payload)

Set a new voice server to connect to.

Parameters:

Name Type Description Default
payload dict

New voice server connection data

required
Source code in interactions/api/voice/voice_gateway.py
401
402
403
404
405
406
407
408
409
410
411
412
def set_new_voice_server(self, payload: dict) -> None:
    """
    Set a new voice server to connect to.

    Args:
        payload: New voice server connection data

    """
    self.ws_url = f"wss://{payload['endpoint']}?v=4"
    self.token = payload["token"]
    self.guild_id = payload["guild_id"]
    self._voice_server_update.set()

speaking(is_speaking=True) async

Tell the gateway if we're sending audio or not.

Parameters:

Name Type Description Default
is_speaking bool

If we're sending audio or not

True
Source code in interactions/api/voice/voice_gateway.py
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
async def speaking(self, is_speaking: bool = True) -> None:
    """
    Tell the gateway if we're sending audio or not.

    Args:
        is_speaking: If we're sending audio or not

    """
    payload = {
        "op": OP.SPEAKING,
        "d": {
            "speaking": 1 << 0 if is_speaking else 0,
            "delay": 0,
            "ssrc": self.ssrc,
        },
    }
    await self.ws.send_json(payload)

This file handles the interaction with discords http endpoints.

BucketLock

Manages the rate limit for each bucket.

Source code in interactions/api/http/http_client.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
class BucketLock:
    """Manages the rate limit for each bucket."""

    DEFAULT_LIMIT = 1
    DEFAULT_REMAINING = 1
    DEFAULT_DELTA = 0.0

    def __init__(self, header: CIMultiDictProxy | None = None) -> None:
        self._semaphore: asyncio.Semaphore | None = None
        if header is None:
            self.bucket_hash: str | None = None
            self.limit: int = self.DEFAULT_LIMIT
            self.remaining: int = self.DEFAULT_REMAINING
            self.delta: float = self.DEFAULT_DELTA
        else:
            self.ingest_ratelimit_header(header)

        self.logger = constants.get_logger()

        self._lock: asyncio.Lock = asyncio.Lock()

    def __repr__(self) -> str:
        return f"<BucketLock: {self.bucket_hash or 'Generic'}, limit: {self.limit}, remaining: {self.remaining}, delta: {self.delta}>"

    @property
    def locked(self) -> bool:
        """Returns whether the bucket is locked."""
        if self._lock.locked():
            return True
        return self._semaphore is not None and self._semaphore.locked()

    def ingest_ratelimit_header(self, header: CIMultiDictProxy) -> None:
        """
        Ingests the rate limit header.

        Args:
            header: The header to ingest, containing rate limit information.

        Updates the bucket_hash, limit, remaining, and delta attributes with the information from the header.

        """
        self.bucket_hash = header.get("x-ratelimit-bucket")
        self.limit = int(header.get("x-ratelimit-limit", self.DEFAULT_LIMIT))
        self.remaining = int(header.get("x-ratelimit-remaining", self.DEFAULT_REMAINING))
        self.delta = float(header.get("x-ratelimit-reset-after", self.DEFAULT_DELTA))

        if self.delta < 0.005 and self.remaining == 0:  # the delta value is so small that we can assume it's 0
            self.delta = self.DEFAULT_DELTA
            self.remaining = self.DEFAULT_REMAINING  # we can assume that we can make another request right away

        if self._semaphore is None or self._semaphore._value != self.limit:
            self._semaphore = asyncio.Semaphore(self.limit)

    async def acquire(self) -> None:
        """Acquires the semaphore."""
        if self._semaphore is None:
            return

        if self._lock.locked():
            self.logger.debug(f"Waiting for bucket {self.bucket_hash} to unlock.")
            async with self._lock:
                pass

        await self._semaphore.acquire()

    def release(self) -> None:
        """
        Releases the semaphore.

        Note: If the bucket has been locked with lock_for_duration, this will not release the lock.
        """
        if self._semaphore is None:
            return
        self._semaphore.release()

    async def lock_for_duration(self, duration: float, block: bool = False) -> None:
        """
        Locks the bucket for a given duration.

        Args:
            duration: The duration to lock the bucket for.
            block: Whether to block until the bucket is unlocked.

        Raises:
            RuntimeError: If the bucket is already locked.

        """
        if self._lock.locked():
            raise RuntimeError("Attempted to lock a bucket that is already locked.")

        async def _release() -> None:
            await asyncio.sleep(duration)
            self._lock.release()

        if block:
            await self._lock.acquire()
            await _release()
        else:
            await self._lock.acquire()
            _ = asyncio.create_task(_release())  # noqa: RUF006

    async def __aenter__(self) -> None:
        await self.acquire()

    async def __aexit__(self, *args) -> None:
        self.release()

locked: bool property

Returns whether the bucket is locked.

acquire() async

Acquires the semaphore.

Source code in interactions/api/http/http_client.py
153
154
155
156
157
158
159
160
161
162
163
async def acquire(self) -> None:
    """Acquires the semaphore."""
    if self._semaphore is None:
        return

    if self._lock.locked():
        self.logger.debug(f"Waiting for bucket {self.bucket_hash} to unlock.")
        async with self._lock:
            pass

    await self._semaphore.acquire()

ingest_ratelimit_header(header)

Ingests the rate limit header.

Parameters:

Name Type Description Default
header CIMultiDictProxy

The header to ingest, containing rate limit information.

required

Updates the bucket_hash, limit, remaining, and delta attributes with the information from the header.

Source code in interactions/api/http/http_client.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
def ingest_ratelimit_header(self, header: CIMultiDictProxy) -> None:
    """
    Ingests the rate limit header.

    Args:
        header: The header to ingest, containing rate limit information.

    Updates the bucket_hash, limit, remaining, and delta attributes with the information from the header.

    """
    self.bucket_hash = header.get("x-ratelimit-bucket")
    self.limit = int(header.get("x-ratelimit-limit", self.DEFAULT_LIMIT))
    self.remaining = int(header.get("x-ratelimit-remaining", self.DEFAULT_REMAINING))
    self.delta = float(header.get("x-ratelimit-reset-after", self.DEFAULT_DELTA))

    if self.delta < 0.005 and self.remaining == 0:  # the delta value is so small that we can assume it's 0
        self.delta = self.DEFAULT_DELTA
        self.remaining = self.DEFAULT_REMAINING  # we can assume that we can make another request right away

    if self._semaphore is None or self._semaphore._value != self.limit:
        self._semaphore = asyncio.Semaphore(self.limit)

lock_for_duration(duration, block=False) async

Locks the bucket for a given duration.

Parameters:

Name Type Description Default
duration float

The duration to lock the bucket for.

required
block bool

Whether to block until the bucket is unlocked.

False

Raises:

Type Description
RuntimeError

If the bucket is already locked.

Source code in interactions/api/http/http_client.py
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
async def lock_for_duration(self, duration: float, block: bool = False) -> None:
    """
    Locks the bucket for a given duration.

    Args:
        duration: The duration to lock the bucket for.
        block: Whether to block until the bucket is unlocked.

    Raises:
        RuntimeError: If the bucket is already locked.

    """
    if self._lock.locked():
        raise RuntimeError("Attempted to lock a bucket that is already locked.")

    async def _release() -> None:
        await asyncio.sleep(duration)
        self._lock.release()

    if block:
        await self._lock.acquire()
        await _release()
    else:
        await self._lock.acquire()
        _ = asyncio.create_task(_release())  # noqa: RUF006

release()

Releases the semaphore.

Note: If the bucket has been locked with lock_for_duration, this will not release the lock.

Source code in interactions/api/http/http_client.py
165
166
167
168
169
170
171
172
173
def release(self) -> None:
    """
    Releases the semaphore.

    Note: If the bucket has been locked with lock_for_duration, this will not release the lock.
    """
    if self._semaphore is None:
        return
    self._semaphore.release()

GlobalLock

Source code in interactions/api/http/http_client.py
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
class GlobalLock:
    def __init__(self) -> None:
        self._lock = asyncio.Lock()
        self.max_requests = 45
        self._calls = self.max_requests
        self._reset_time = 0

    @property
    def calls_remaining(self) -> int:
        """Returns the amount of calls remaining."""
        return self.max_requests - self._calls

    def reset_calls(self) -> None:
        """Resets the calls to the max amount."""
        self._calls = self.max_requests
        self._reset_time = time.perf_counter() + 1

    def set_reset_time(self, delta: float) -> None:
        """
        Sets the reset time to the current time + delta.

        To be called if a 429 is received.

        Args:
            delta: The time to wait before resetting the calls.

        """
        self._reset_time = time.perf_counter() + delta
        self._calls = 0

    async def wait(self) -> None:
        """Throttles calls to prevent hitting the global rate limit."""
        async with self._lock:
            if self._reset_time <= time.perf_counter():
                self.reset_calls()
            elif self._calls <= 0:
                await asyncio.sleep(self._reset_time - time.perf_counter())
                self.reset_calls()
        self._calls -= 1

calls_remaining: int property

Returns the amount of calls remaining.

reset_calls()

Resets the calls to the max amount.

Source code in interactions/api/http/http_client.py
71
72
73
74
def reset_calls(self) -> None:
    """Resets the calls to the max amount."""
    self._calls = self.max_requests
    self._reset_time = time.perf_counter() + 1

set_reset_time(delta)

Sets the reset time to the current time + delta.

To be called if a 429 is received.

Parameters:

Name Type Description Default
delta float

The time to wait before resetting the calls.

required
Source code in interactions/api/http/http_client.py
76
77
78
79
80
81
82
83
84
85
86
87
def set_reset_time(self, delta: float) -> None:
    """
    Sets the reset time to the current time + delta.

    To be called if a 429 is received.

    Args:
        delta: The time to wait before resetting the calls.

    """
    self._reset_time = time.perf_counter() + delta
    self._calls = 0

wait() async

Throttles calls to prevent hitting the global rate limit.

Source code in interactions/api/http/http_client.py
89
90
91
92
93
94
95
96
97
async def wait(self) -> None:
    """Throttles calls to prevent hitting the global rate limit."""
    async with self._lock:
        if self._reset_time <= time.perf_counter():
            self.reset_calls()
        elif self._calls <= 0:
            await asyncio.sleep(self._reset_time - time.perf_counter())
            self.reset_calls()
    self._calls -= 1

HTTPClient

Bases: BotRequests, ChannelRequests, EmojiRequests, EntitlementRequests, GuildRequests, InteractionRequests, MemberRequests, MessageRequests, ReactionRequests, StickerRequests, ThreadRequests, UserRequests, WebhookRequests, ScheduledEventsRequests

A http client for sending requests to the Discord API.

Source code in interactions/api/http/http_client.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
class HTTPClient(
    BotRequests,
    ChannelRequests,
    EmojiRequests,
    EntitlementRequests,
    GuildRequests,
    InteractionRequests,
    MemberRequests,
    MessageRequests,
    ReactionRequests,
    StickerRequests,
    ThreadRequests,
    UserRequests,
    WebhookRequests,
    ScheduledEventsRequests,
):
    """A http client for sending requests to the Discord API."""

    def __init__(
        self,
        connector: BaseConnector | None = None,
        logger: Logger = MISSING,
        show_ratelimit_tracebacks: bool = False,
        proxy: tuple[str | None, BasicAuth | None] | None = None,
    ) -> None:
        self.connector: BaseConnector | None = connector
        self.__session: ClientSession | None = None
        self.token: str | None = None
        self.global_lock: GlobalLock = GlobalLock()
        self._max_attempts: int = 3

        self.ratelimit_locks: WeakValueDictionary[str, BucketLock] = WeakValueDictionary()
        self.show_ratelimit_traceback: bool = show_ratelimit_tracebacks
        self._endpoints = {}

        self.user_agent: str = (
            f"DiscordBot ({__repo_url__} {__version__} Python/{__py_version__}) aiohttp/{aiohttp.__version__}"
        )
        self.proxy: tuple[str | None, BasicAuth | None] | None = proxy
        self.__proxy_validated: bool = False

        if logger is MISSING:
            logger = constants.get_logger()
        self.logger = logger

    def get_ratelimit(self, route: Route) -> BucketLock:
        """
        Get a route's rate limit bucket.

        Args:
            route: The route to fetch the ratelimit bucket for

        Returns:
            The BucketLock object for this route

        """
        if bucket_hash := self._endpoints.get(route.rl_bucket):
            if lock := self.ratelimit_locks.get(bucket_hash):
                # if we have an active lock on this route, it'll still be in the cache
                # return that lock
                return lock
        # if no cached lock exists, return a new lock
        return BucketLock()

    def ingest_ratelimit(self, route: Route, header: CIMultiDictProxy, bucket_lock: BucketLock) -> None:
        """
        Ingests a ratelimit header from discord to determine ratelimit.

        Args:
            route: The route we're ingesting ratelimit for
            header: The rate limit header in question
            bucket_lock: The rate limit bucket for this route

        """
        bucket_lock.ingest_ratelimit_header(header)

        if bucket_lock.bucket_hash:
            # We only ever try and cache the bucket if the bucket hash has been set (ignores unlimited endpoints)
            self.logger.debug(f"Caching ingested rate limit data for: {bucket_lock.bucket_hash}")
            self._endpoints[route.rl_bucket] = bucket_lock.bucket_hash
            self.ratelimit_locks[bucket_lock.bucket_hash] = bucket_lock

    @staticmethod
    def _process_payload(
        payload: dict | list[dict] | None, files: UPLOADABLE_TYPE | list[UPLOADABLE_TYPE] | None
    ) -> dict | list[dict] | FormData | None:
        """
        Processes a payload into a format safe for discord. Converts the payload into FormData where required

        Args:
            payload: The payload of the request
            files: A list of any files to send

        Returns:
            Either a dictionary or multipart data form

        """
        if isinstance(payload, FormData):
            return payload
        if payload is None:
            return None

        if isinstance(payload, dict):
            payload = dict_filter(payload)

            for k, v in payload.items():
                if isinstance(v, DictSerializationMixin):
                    payload[k] = v.to_dict()
                if isinstance(v, (list, tuple, set)):
                    payload[k] = [i.to_dict() if isinstance(i, DictSerializationMixin) else i for i in v]

        else:
            payload = [dict_filter(x) if isinstance(x, dict) else x for x in payload]

        if files is None:
            return payload

        if files == []:
            payload["attachments"] = []
            return payload

        if not isinstance(files, list):
            files = (files,)

        attachments = []

        form_data = FormData(quote_fields=False)

        for index, file in enumerate(files):
            file_data = models.open_file(file).read()

            if isinstance(file, models.File):
                form_data.add_field(
                    f"files[{index}]",
                    file_data,
                    filename=file.file_name,
                    content_type=file.content_type or get_file_mimetype(file_data),
                )
                attachments.append({"id": index, "description": file.description, "filename": file.file_name})
            else:
                form_data.add_field(
                    f"files[{index}]",
                    file_data,
                    filename=file.split(os.sep)[-1],
                    content_type=get_file_mimetype(file_data),
                )
        if attachments:
            payload["attachments"] = attachments

        form_data.add_field("payload_json", FastJson.dumps(payload))
        return form_data

    async def request(  # noqa: C901
        self,
        route: Route,
        payload: list | dict | None = None,
        files: list[UPLOADABLE_TYPE] | None = None,
        reason: str | None = None,
        params: dict | None = None,
        **kwargs: dict,
    ) -> str | dict[str, Any] | None:
        """
        Make a request to discord.

        Args:
            route: The route to take
            payload: The payload for this request
            files: The files to send with this request
            reason: Attach a reason to this request, used for audit logs
            params: Query string parameters

        """
        # Assemble headers
        kwargs["headers"] = {"User-Agent": self.user_agent}
        if self.token:
            kwargs["headers"]["Authorization"] = f"Bot {self.token}"
        if reason:
            kwargs["headers"]["X-Audit-Log-Reason"] = _uriquote(reason, safe="/ ")

        if isinstance(payload, (list, dict)) and not files:
            kwargs["headers"]["Content-Type"] = "application/json"
        if isinstance(params, dict):
            kwargs["params"] = dict_filter(params)

        lock = self.get_ratelimit(route)
        # this gets a BucketLock for this route.
        # If this endpoint has been used before, it will get an existing ratelimit for the respective buckethash
        # otherwise a brand-new bucket lock will be returned

        for attempt in range(self._max_attempts):
            async with lock:
                try:
                    if self.__session.closed:
                        await self.login(cast(str, self.token))

                    processed_data = self._process_payload(payload, files)
                    if isinstance(processed_data, FormData):
                        kwargs["data"] = processed_data  # pyright: ignore
                    else:
                        kwargs["json"] = processed_data  # pyright: ignore
                    await self.global_lock.wait()

                    if self.proxy:
                        kwargs["proxy"] = self.proxy[0]
                        kwargs["proxy_auth"] = self.proxy[1]

                    async with self.__session.request(route.method, route.url, **kwargs) as response:
                        result = await response_decode(response)
                        self.ingest_ratelimit(route, response.headers, lock)

                        if response.status == 429:
                            # ratelimit exceeded
                            result = cast(dict[str, str], result)
                            if result.get("global", False):
                                # global ratelimit is reached
                                # if we get a global, that's pretty bad, this would usually happen if the user is hitting the api from 2 clients sharing a token
                                self.log_ratelimit(
                                    self.logger.warning,
                                    f"Bot has exceeded global ratelimit, locking REST API for {result['retry_after']} seconds",
                                )
                                self.global_lock.set_reset_time(float(result["retry_after"]))
                            elif result.get("message") == "The resource is being rate limited.":
                                # resource ratelimit is reached
                                self.log_ratelimit(
                                    self.logger.warning,
                                    f"{route.resolved_endpoint} The resource is being rate limited! "
                                    f"Reset in {result.get('retry_after')} seconds",
                                )
                                # lock this resource and wait for unlock
                                await lock.lock_for_duration(float(result["retry_after"]), block=True)
                            else:
                                # endpoint ratelimit is reached
                                # 429's are unfortunately unavoidable, but we can attempt to avoid them
                                # so long as these are infrequent we're doing well
                                self.log_ratelimit(
                                    self.logger.warning,
                                    f"{route.resolved_endpoint} Has exceeded its ratelimit ({lock.limit})! Reset in {lock.delta} seconds",
                                )
                                await lock.lock_for_duration(lock.delta, block=True)
                            continue
                        if lock.remaining == 0:
                            # Last call available in the bucket, lock until reset
                            self.log_ratelimit(
                                self.logger.debug,
                                f"{route.resolved_endpoint} Has exhausted its ratelimit ({lock.limit})! Locking route for {lock.delta} seconds",
                            )
                            await lock.lock_for_duration(
                                lock.delta
                            )  # lock this route, but continue processing the current response

                        elif response.status in {500, 502, 504}:
                            # Server issues, retry
                            self.logger.warning(
                                f"{route.resolved_endpoint} Received {response.status}... retrying in {1 + attempt * 2} seconds"
                            )
                            await asyncio.sleep(1 + attempt * 2)
                            continue

                        if not 300 > response.status >= 200:
                            await self._raise_exception(response, route, result)

                        self.logger.debug(
                            f"{route.resolved_endpoint} Received {response.status} :: [{lock.remaining}/{lock.limit} calls remaining]"
                        )
                        return result
                except OSError as e:
                    if attempt < self._max_attempts - 1 and e.errno in (54, 10054):
                        await asyncio.sleep(1 + attempt * 2)
                        continue
                    raise

    async def _raise_exception(self, response, route, result) -> None:
        self.logger.error(f"{route.method}::{route.url}: {response.status}")

        if response.status == 403:
            raise Forbidden(response, response_data=result, route=route)
        if response.status == 404:
            raise NotFound(response, response_data=result, route=route)
        if response.status >= 500:
            raise DiscordError(response, response_data=result, route=route)
        raise HTTPException(response, response_data=result, route=route)

    def log_ratelimit(self, log_func: Callable, message: str) -> None:
        """
        Logs a ratelimit message, optionally with a traceback if show_ratelimit_traceback is True

        Args:
            log_func: The logging function to use
            message: The message to log

        """
        if self.show_ratelimit_traceback:
            if frame := next(
                (frame for frame in inspect.stack() if constants.LIB_PATH not in frame.filename),
                None,
            ):
                frame_info = inspect.getframeinfo(frame[0])
                filename = os.path.relpath(frame_info.filename, os.getcwd())

                traceback = (
                    f"{filename}:{frame_info.lineno} in {frame_info.function}:: {frame_info.code_context[0].strip()}"
                )
                message = f"{message} | Caused By: {traceback}"

        log_func(message)

    async def request_cdn(self, url, asset) -> bytes:  # pyright: ignore [reportGeneralTypeIssues]
        self.logger.debug(f"{asset} requests {url} from CDN")
        async with self.__session.get(url) as response:
            if response.status == 200:
                return await response.read()
            await self._raise_exception(response, asset, await response_decode(response))

    async def login(self, token: str) -> dict[str, Any]:
        """
        "Login" to the gateway, basically validates the token and grabs user data.

        Args:
            token: the token to use

        Returns:
            The currently logged in bot's data

        """
        self.__session = ClientSession(
            connector=self.connector or aiohttp.TCPConnector(limit=self.global_lock.max_requests),
            json_serialize=FastJson.dumps,
        )
        if not self.__proxy_validated and self.proxy:
            try:
                self.logger.info(f"Validating Proxy @ {self.proxy[0]}")
                async with self.__session.get(
                    "http://icanhazip.com/", proxy=self.proxy[0], proxy_auth=self.proxy[1]
                ) as response:
                    if response.status != 200:
                        raise RuntimeError("Proxy configuration is invalid")
                    self.logger.info(f"Proxy Connected @ {(await response.text()).strip()}")
                    self.__proxy_validated = True
            except Exception as e:
                raise RuntimeError("Proxy configuration is invalid") from e

        self.token = token
        try:
            result = await self.request(Route("GET", "/users/@me"))
            return cast(dict[str, Any], result)
        except HTTPException as e:
            if e.status == 401:
                raise LoginError("An improper token was passed") from e
            raise

    async def close(self) -> None:
        """Close the session."""
        if self.__session and not self.__session.closed:
            await self.__session.close()

    async def get_gateway(self) -> str:
        """
        Gets the gateway url.

        Returns:
            The gateway url

        """
        try:
            result = await self.request(Route("GET", "/gateway"))
            result = cast(dict[str, Any], result)
        except HTTPException as exc:
            raise GatewayNotFound from exc
        return "{0}?encoding={1}&v={2}&compress=zlib-stream".format(result["url"], "json", __api_version__)

    async def get_gateway_bot(self) -> discord_typings.GetGatewayBotData:
        try:
            result = await self.request(Route("GET", "/gateway/bot"))
        except HTTPException as exc:
            raise GatewayNotFound from exc
        return cast(discord_typings.GetGatewayBotData, result)

    async def websocket_connect(self, url: str) -> ClientWebSocketResponse:
        """
        Connect to the websocket.

        Args:
            url: the url to connect to

        """
        return await self.__session.ws_connect(
            url,
            timeout=30,
            max_msg_size=0,
            autoclose=False,
            headers={"User-Agent": self.user_agent},
            compress=0,
            proxy=self.proxy[0] if self.proxy else None,
            proxy_auth=self.proxy[1] if self.proxy else None,
        )

close() async

Close the session.

Source code in interactions/api/http/http_client.py
558
559
560
561
async def close(self) -> None:
    """Close the session."""
    if self.__session and not self.__session.closed:
        await self.__session.close()

get_gateway() async

Gets the gateway url.

Returns:

Type Description
str

The gateway url

Source code in interactions/api/http/http_client.py
563
564
565
566
567
568
569
570
571
572
573
574
575
576
async def get_gateway(self) -> str:
    """
    Gets the gateway url.

    Returns:
        The gateway url

    """
    try:
        result = await self.request(Route("GET", "/gateway"))
        result = cast(dict[str, Any], result)
    except HTTPException as exc:
        raise GatewayNotFound from exc
    return "{0}?encoding={1}&v={2}&compress=zlib-stream".format(result["url"], "json", __api_version__)

get_ratelimit(route)

Get a route's rate limit bucket.

Parameters:

Name Type Description Default
route Route

The route to fetch the ratelimit bucket for

required

Returns:

Type Description
BucketLock

The BucketLock object for this route

Source code in interactions/api/http/http_client.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
def get_ratelimit(self, route: Route) -> BucketLock:
    """
    Get a route's rate limit bucket.

    Args:
        route: The route to fetch the ratelimit bucket for

    Returns:
        The BucketLock object for this route

    """
    if bucket_hash := self._endpoints.get(route.rl_bucket):
        if lock := self.ratelimit_locks.get(bucket_hash):
            # if we have an active lock on this route, it'll still be in the cache
            # return that lock
            return lock
    # if no cached lock exists, return a new lock
    return BucketLock()

ingest_ratelimit(route, header, bucket_lock)

Ingests a ratelimit header from discord to determine ratelimit.

Parameters:

Name Type Description Default
route Route

The route we're ingesting ratelimit for

required
header CIMultiDictProxy

The rate limit header in question

required
bucket_lock BucketLock

The rate limit bucket for this route

required
Source code in interactions/api/http/http_client.py
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
def ingest_ratelimit(self, route: Route, header: CIMultiDictProxy, bucket_lock: BucketLock) -> None:
    """
    Ingests a ratelimit header from discord to determine ratelimit.

    Args:
        route: The route we're ingesting ratelimit for
        header: The rate limit header in question
        bucket_lock: The rate limit bucket for this route

    """
    bucket_lock.ingest_ratelimit_header(header)

    if bucket_lock.bucket_hash:
        # We only ever try and cache the bucket if the bucket hash has been set (ignores unlimited endpoints)
        self.logger.debug(f"Caching ingested rate limit data for: {bucket_lock.bucket_hash}")
        self._endpoints[route.rl_bucket] = bucket_lock.bucket_hash
        self.ratelimit_locks[bucket_lock.bucket_hash] = bucket_lock

log_ratelimit(log_func, message)

Logs a ratelimit message, optionally with a traceback if show_ratelimit_traceback is True

Parameters:

Name Type Description Default
log_func Callable

The logging function to use

required
message str

The message to log

required
Source code in interactions/api/http/http_client.py
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
def log_ratelimit(self, log_func: Callable, message: str) -> None:
    """
    Logs a ratelimit message, optionally with a traceback if show_ratelimit_traceback is True

    Args:
        log_func: The logging function to use
        message: The message to log

    """
    if self.show_ratelimit_traceback:
        if frame := next(
            (frame for frame in inspect.stack() if constants.LIB_PATH not in frame.filename),
            None,
        ):
            frame_info = inspect.getframeinfo(frame[0])
            filename = os.path.relpath(frame_info.filename, os.getcwd())

            traceback = (
                f"{filename}:{frame_info.lineno} in {frame_info.function}:: {frame_info.code_context[0].strip()}"
            )
            message = f"{message} | Caused By: {traceback}"

    log_func(message)

login(token) async

"Login" to the gateway, basically validates the token and grabs user data.

Parameters:

Name Type Description Default
token str

the token to use

required

Returns:

Type Description
dict[str, Any]

The currently logged in bot's data

Source code in interactions/api/http/http_client.py
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
async def login(self, token: str) -> dict[str, Any]:
    """
    "Login" to the gateway, basically validates the token and grabs user data.

    Args:
        token: the token to use

    Returns:
        The currently logged in bot's data

    """
    self.__session = ClientSession(
        connector=self.connector or aiohttp.TCPConnector(limit=self.global_lock.max_requests),
        json_serialize=FastJson.dumps,
    )
    if not self.__proxy_validated and self.proxy:
        try:
            self.logger.info(f"Validating Proxy @ {self.proxy[0]}")
            async with self.__session.get(
                "http://icanhazip.com/", proxy=self.proxy[0], proxy_auth=self.proxy[1]
            ) as response:
                if response.status != 200:
                    raise RuntimeError("Proxy configuration is invalid")
                self.logger.info(f"Proxy Connected @ {(await response.text()).strip()}")
                self.__proxy_validated = True
        except Exception as e:
            raise RuntimeError("Proxy configuration is invalid") from e

    self.token = token
    try:
        result = await self.request(Route("GET", "/users/@me"))
        return cast(dict[str, Any], result)
    except HTTPException as e:
        if e.status == 401:
            raise LoginError("An improper token was passed") from e
        raise

request(route, payload=None, files=None, reason=None, params=None, **kwargs) async

Make a request to discord.

Parameters:

Name Type Description Default
route Route

The route to take

required
payload list | dict | None

The payload for this request

None
files list[UPLOADABLE_TYPE] | None

The files to send with this request

None
reason str | None

Attach a reason to this request, used for audit logs

None
params dict | None

Query string parameters

None
Source code in interactions/api/http/http_client.py
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
async def request(  # noqa: C901
    self,
    route: Route,
    payload: list | dict | None = None,
    files: list[UPLOADABLE_TYPE] | None = None,
    reason: str | None = None,
    params: dict | None = None,
    **kwargs: dict,
) -> str | dict[str, Any] | None:
    """
    Make a request to discord.

    Args:
        route: The route to take
        payload: The payload for this request
        files: The files to send with this request
        reason: Attach a reason to this request, used for audit logs
        params: Query string parameters

    """
    # Assemble headers
    kwargs["headers"] = {"User-Agent": self.user_agent}
    if self.token:
        kwargs["headers"]["Authorization"] = f"Bot {self.token}"
    if reason:
        kwargs["headers"]["X-Audit-Log-Reason"] = _uriquote(reason, safe="/ ")

    if isinstance(payload, (list, dict)) and not files:
        kwargs["headers"]["Content-Type"] = "application/json"
    if isinstance(params, dict):
        kwargs["params"] = dict_filter(params)

    lock = self.get_ratelimit(route)
    # this gets a BucketLock for this route.
    # If this endpoint has been used before, it will get an existing ratelimit for the respective buckethash
    # otherwise a brand-new bucket lock will be returned

    for attempt in range(self._max_attempts):
        async with lock:
            try:
                if self.__session.closed:
                    await self.login(cast(str, self.token))

                processed_data = self._process_payload(payload, files)
                if isinstance(processed_data, FormData):
                    kwargs["data"] = processed_data  # pyright: ignore
                else:
                    kwargs["json"] = processed_data  # pyright: ignore
                await self.global_lock.wait()

                if self.proxy:
                    kwargs["proxy"] = self.proxy[0]
                    kwargs["proxy_auth"] = self.proxy[1]

                async with self.__session.request(route.method, route.url, **kwargs) as response:
                    result = await response_decode(response)
                    self.ingest_ratelimit(route, response.headers, lock)

                    if response.status == 429:
                        # ratelimit exceeded
                        result = cast(dict[str, str], result)
                        if result.get("global", False):
                            # global ratelimit is reached
                            # if we get a global, that's pretty bad, this would usually happen if the user is hitting the api from 2 clients sharing a token
                            self.log_ratelimit(
                                self.logger.warning,
                                f"Bot has exceeded global ratelimit, locking REST API for {result['retry_after']} seconds",
                            )
                            self.global_lock.set_reset_time(float(result["retry_after"]))
                        elif result.get("message") == "The resource is being rate limited.":
                            # resource ratelimit is reached
                            self.log_ratelimit(
                                self.logger.warning,
                                f"{route.resolved_endpoint} The resource is being rate limited! "
                                f"Reset in {result.get('retry_after')} seconds",
                            )
                            # lock this resource and wait for unlock
                            await lock.lock_for_duration(float(result["retry_after"]), block=True)
                        else:
                            # endpoint ratelimit is reached
                            # 429's are unfortunately unavoidable, but we can attempt to avoid them
                            # so long as these are infrequent we're doing well
                            self.log_ratelimit(
                                self.logger.warning,
                                f"{route.resolved_endpoint} Has exceeded its ratelimit ({lock.limit})! Reset in {lock.delta} seconds",
                            )
                            await lock.lock_for_duration(lock.delta, block=True)
                        continue
                    if lock.remaining == 0:
                        # Last call available in the bucket, lock until reset
                        self.log_ratelimit(
                            self.logger.debug,
                            f"{route.resolved_endpoint} Has exhausted its ratelimit ({lock.limit})! Locking route for {lock.delta} seconds",
                        )
                        await lock.lock_for_duration(
                            lock.delta
                        )  # lock this route, but continue processing the current response

                    elif response.status in {500, 502, 504}:
                        # Server issues, retry
                        self.logger.warning(
                            f"{route.resolved_endpoint} Received {response.status}... retrying in {1 + attempt * 2} seconds"
                        )
                        await asyncio.sleep(1 + attempt * 2)
                        continue

                    if not 300 > response.status >= 200:
                        await self._raise_exception(response, route, result)

                    self.logger.debug(
                        f"{route.resolved_endpoint} Received {response.status} :: [{lock.remaining}/{lock.limit} calls remaining]"
                    )
                    return result
            except OSError as e:
                if attempt < self._max_attempts - 1 and e.errno in (54, 10054):
                    await asyncio.sleep(1 + attempt * 2)
                    continue
                raise

websocket_connect(url) async

Connect to the websocket.

Parameters:

Name Type Description Default
url str

the url to connect to

required
Source code in interactions/api/http/http_client.py
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
async def websocket_connect(self, url: str) -> ClientWebSocketResponse:
    """
    Connect to the websocket.

    Args:
        url: the url to connect to

    """
    return await self.__session.ws_connect(
        url,
        timeout=30,
        max_msg_size=0,
        autoclose=False,
        headers={"User-Agent": self.user_agent},
        compress=0,
        proxy=self.proxy[0] if self.proxy else None,
        proxy_auth=self.proxy[1] if self.proxy else None,
    )

Voice

Audio

Bases: BaseAudio

Audio for playing from file or URL.

Source code in interactions/api/voice/audio.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
class Audio(BaseAudio):
    """Audio for playing from file or URL."""

    source: str
    """The source ffmpeg should use to play the audio"""
    process: subprocess.Popen
    """The ffmpeg process to use"""
    buffer: AudioBuffer
    """The audio objects buffer to prevent stuttering"""
    buffer_seconds: int
    """How many seconds of audio should be buffered"""
    read_ahead_task: threading.Thread
    """A thread that reads ahead to create the buffer"""
    probe: bool
    """Should ffprobe be used to detect audio data?"""
    ffmpeg_args: str | list[str]
    """Args to pass to ffmpeg"""
    ffmpeg_before_args: str | list[str]
    """Args to pass to ffmpeg before the source"""

    def __init__(self, src: Union[str, Path]) -> None:
        self.source = src
        self.needs_encode = True
        self.locked_stream = False
        self.process: Optional[subprocess.Popen] = None

        self.buffer = AudioBuffer()

        self.buffer_seconds = 3
        self.read_ahead_task = threading.Thread(target=self._read_ahead, daemon=True)

        self.ffmpeg_before_args = ""
        self.ffmpeg_args = ""
        self.probe: bool = False

    def __repr__(self) -> str:
        return f"<{type(self).__name__}: {self.source}>"

    @property
    def _max_buffer_size(self) -> int:
        # 1ms of audio * (buffer seconds * 1000)
        return 192 * (self.buffer_seconds * 1000)

    @property
    def audio_complete(self) -> bool:
        """Uses the state of the subprocess to determine if more audio is coming"""
        return not self.process or self.process.poll() is not None

    def _create_process(self, *, block: bool = True) -> None:
        before = (
            self.ffmpeg_before_args if isinstance(self.ffmpeg_before_args, list) else self.ffmpeg_before_args.split()
        )

        config = {
            "sample_rate": 48000,
            "bitrate": None,
        }

        if shutil.which("ffprobe") is not None and self.probe:
            ffprobe_cmd = [
                "ffprobe",
                "-loglevel",
                "quiet",
                "-print_format",
                "json",
                "-show_streams",
                "-select_streams",
                "a:0",
                self.source,
            ]
            raw_output = subprocess.check_output(ffprobe_cmd, stderr=subprocess.DEVNULL)
            output = FastJson.loads(raw_output)

            config["sample_rate"] = int(output["streams"][0]["sample_rate"])
            config["bitrate"] = int(output["streams"][0]["bit_rate"])

            get_logger().debug(f"Detected audio data for {self.source} - {config}")

            if getattr(self, "bitrate", None) is None and self.encoder:
                self.bitrate = int(config["bitrate"] / 1024)
                self.encoder.set_bitrate(self.bitrate)

        after = self.ffmpeg_args if isinstance(self.ffmpeg_args, list) else self.ffmpeg_args.split()
        cmd = [
            "ffmpeg",
            "-i",
            self.source,
            "-f",
            "s16le",
            "-ar",
            str(config["sample_rate"]),
            "-ac",
            "2",
            "-loglevel",
            "warning",
            "pipe:1",
            "-vn",
        ]
        cmd[1:1] = before
        cmd.extend(after)

        self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
        self.read_ahead_task.start()

        if block:
            # block until some data is in the buffer
            self.buffer.initialised.wait()

    def _read_ahead(self) -> None:
        while self.process:
            if self.process.poll() is not None:
                # ffmpeg has exited, stop reading ahead
                if not self.buffer.initialised.is_set():
                    # assume this is a small file and initialise the buffer
                    self.buffer.initialised.set()

                return
            if len(self.buffer) < self._max_buffer_size:
                self.buffer.extend(self.process.stdout.read(3840))
            else:
                if not self.buffer.initialised.is_set():
                    self.buffer.initialised.set()
                time.sleep(0.1)

    def pre_buffer(self, duration: None | float = None) -> None:
        """
        Start pre-buffering the audio.

        Args:
            duration: The duration of audio to pre-buffer.

        """
        if duration:
            self.buffer_seconds = duration

        if self.process and self.process.poll() is None:
            raise RuntimeError("Cannot pre-buffer an already running process")
        # sanity value enforcement to prevent audio weirdness
        self.buffer = AudioBuffer()
        self.buffer.initialised.clear()

        self._create_process(block=False)

    def read(self, frame_size: int) -> bytes:
        """
        Reads frame_size bytes of audio from the buffer.

        Returns:
            bytes of audio

        """
        if not self.process:
            self._create_process()
        if not self.buffer.initialised.is_set():
            # we cannot start playing until the buffer is initialised
            self.buffer.initialised.wait()

        data = self.buffer.read(frame_size)

        if len(data) != frame_size:
            data = b""

        return bytes(data)

    def cleanup(self) -> None:
        """Cleans up after this audio object."""
        if self.process and self.process.poll() is None:
            self.process.kill()
            self.process.wait()

audio_complete: bool property

Uses the state of the subprocess to determine if more audio is coming

cleanup()

Cleans up after this audio object.

Source code in interactions/api/voice/audio.py
345
346
347
348
349
def cleanup(self) -> None:
    """Cleans up after this audio object."""
    if self.process and self.process.poll() is None:
        self.process.kill()
        self.process.wait()

pre_buffer(duration=None)

Start pre-buffering the audio.

Parameters:

Name Type Description Default
duration None | float

The duration of audio to pre-buffer.

None
Source code in interactions/api/voice/audio.py
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
def pre_buffer(self, duration: None | float = None) -> None:
    """
    Start pre-buffering the audio.

    Args:
        duration: The duration of audio to pre-buffer.

    """
    if duration:
        self.buffer_seconds = duration

    if self.process and self.process.poll() is None:
        raise RuntimeError("Cannot pre-buffer an already running process")
    # sanity value enforcement to prevent audio weirdness
    self.buffer = AudioBuffer()
    self.buffer.initialised.clear()

    self._create_process(block=False)

read(frame_size)

Reads frame_size bytes of audio from the buffer.

Returns:

Type Description
bytes

bytes of audio

Source code in interactions/api/voice/audio.py
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
def read(self, frame_size: int) -> bytes:
    """
    Reads frame_size bytes of audio from the buffer.

    Returns:
        bytes of audio

    """
    if not self.process:
        self._create_process()
    if not self.buffer.initialised.is_set():
        # we cannot start playing until the buffer is initialised
        self.buffer.initialised.wait()

    data = self.buffer.read(frame_size)

    if len(data) != frame_size:
        data = b""

    return bytes(data)

AudioBuffer

Source code in interactions/api/voice/audio.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
class AudioBuffer:
    def __init__(self) -> None:
        self._buffer = bytearray()
        self._lock = threading.Lock()
        self.initialised = threading.Event()

    def __len__(self) -> int:
        return len(self._buffer)

    def extend(self, data: bytes) -> None:
        """
        Extend the buffer with additional data.

        Args:
            data: The data to add

        """
        with self._lock:
            self._buffer.extend(data)

    def read(self, total_bytes: int, *, pad: bool = True) -> bytearray:
        """
        Read `total_bytes` bytes of audio from the buffer.

        Args:
            total_bytes: Amount of bytes to read.
            pad: Whether to pad incomplete frames with 0's.

        Returns:
            Desired amount of bytes

        Raises:
            ValueError: If `pad` is False and the buffer does not contain enough data.

        """
        with self._lock:
            view = memoryview(self._buffer)
            self._buffer = bytearray(view[total_bytes:])
            data = bytearray(view[:total_bytes])
            if 0 < len(data) < total_bytes:
                if pad:
                    # pad incomplete frames with 0's
                    data.extend(b"\0" * (total_bytes - len(data)))
                else:
                    raise ValueError(
                        f"Buffer does not contain enough data to fulfill request {len(data)} < {total_bytes}"
                    )
            return data

    def read_max(self, total_bytes: int) -> bytearray:
        """
        Read up to `total_bytes` bytes of audio from the buffer.

        Args:
            total_bytes: Maximum amount of bytes to read.

        Returns:
            Desired amount of bytes

        Raises:
            EOFError: If the buffer is empty.

        """
        with self._lock:
            if len(self._buffer) == 0:
                raise EOFError("Buffer is empty")
            view = memoryview(self._buffer)
            self._buffer = bytearray(view[total_bytes:])
            return bytearray(view[:total_bytes])

extend(data)

Extend the buffer with additional data.

Parameters:

Name Type Description Default
data bytes

The data to add

required
Source code in interactions/api/voice/audio.py
82
83
84
85
86
87
88
89
90
91
def extend(self, data: bytes) -> None:
    """
    Extend the buffer with additional data.

    Args:
        data: The data to add

    """
    with self._lock:
        self._buffer.extend(data)

read(total_bytes, *, pad=True)

Read total_bytes bytes of audio from the buffer.

Parameters:

Name Type Description Default
total_bytes int

Amount of bytes to read.

required
pad bool

Whether to pad incomplete frames with 0's.

True

Returns:

Type Description
bytearray

Desired amount of bytes

Raises:

Type Description
ValueError

If pad is False and the buffer does not contain enough data.

Source code in interactions/api/voice/audio.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def read(self, total_bytes: int, *, pad: bool = True) -> bytearray:
    """
    Read `total_bytes` bytes of audio from the buffer.

    Args:
        total_bytes: Amount of bytes to read.
        pad: Whether to pad incomplete frames with 0's.

    Returns:
        Desired amount of bytes

    Raises:
        ValueError: If `pad` is False and the buffer does not contain enough data.

    """
    with self._lock:
        view = memoryview(self._buffer)
        self._buffer = bytearray(view[total_bytes:])
        data = bytearray(view[:total_bytes])
        if 0 < len(data) < total_bytes:
            if pad:
                # pad incomplete frames with 0's
                data.extend(b"\0" * (total_bytes - len(data)))
            else:
                raise ValueError(
                    f"Buffer does not contain enough data to fulfill request {len(data)} < {total_bytes}"
                )
        return data

read_max(total_bytes)

Read up to total_bytes bytes of audio from the buffer.

Parameters:

Name Type Description Default
total_bytes int

Maximum amount of bytes to read.

required

Returns:

Type Description
bytearray

Desired amount of bytes

Raises:

Type Description
EOFError

If the buffer is empty.

Source code in interactions/api/voice/audio.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
def read_max(self, total_bytes: int) -> bytearray:
    """
    Read up to `total_bytes` bytes of audio from the buffer.

    Args:
        total_bytes: Maximum amount of bytes to read.

    Returns:
        Desired amount of bytes

    Raises:
        EOFError: If the buffer is empty.

    """
    with self._lock:
        if len(self._buffer) == 0:
            raise EOFError("Buffer is empty")
        view = memoryview(self._buffer)
        self._buffer = bytearray(view[total_bytes:])
        return bytearray(view[:total_bytes])

AudioVolume

Bases: Audio

An audio object with volume control

Source code in interactions/api/voice/audio.py
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
class AudioVolume(Audio):
    """An audio object with volume control"""

    _volume: float
    """The internal volume level of the audio"""

    def __init__(self, src: Union[str, Path]) -> None:
        super().__init__(src)
        self._volume = 0.5

    @property
    def volume(self) -> float:
        """The volume of the audio"""
        return self._volume

    @volume.setter
    def volume(self, value: float) -> None:
        """Sets the volume of the audio. Volume cannot be negative."""
        self._volume = max(value, 0.0)

    def read(self, frame_size: int) -> bytes:
        """
        Reads frame_size ms of audio from source.

        Returns:
            bytes of audio

        """
        data = super().read(frame_size)
        return audioop.mul(data, 2, self._volume)

volume: float writable property

The volume of the audio

read(frame_size)

Reads frame_size ms of audio from source.

Returns:

Type Description
bytes

bytes of audio

Source code in interactions/api/voice/audio.py
372
373
374
375
376
377
378
379
380
381
def read(self, frame_size: int) -> bytes:
    """
    Reads frame_size ms of audio from source.

    Returns:
        bytes of audio

    """
    data = super().read(frame_size)
    return audioop.mul(data, 2, self._volume)

BaseAudio

Bases: ABC

Base structure of the audio.

Source code in interactions/api/voice/audio.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
class BaseAudio(ABC):
    """Base structure of the audio."""

    locked_stream: bool
    """Prevents the audio task from closing automatically when no data is received."""
    needs_encode: bool
    """Does this audio data need encoding with opus?"""
    bitrate: Optional[int]
    """Optionally specify a specific bitrate to encode this audio data with"""
    encoder: Optional[Encoder]
    """The encoder to use for this audio data"""

    def __del__(self) -> None:
        self.cleanup()

    @abstractmethod
    def cleanup(self) -> None:
        """A method to optionally cleanup after this object is no longer required."""
        ...

    @property
    def audio_complete(self) -> bool:
        """A property to tell the player if more audio is expected."""
        return False

    @abstractmethod
    def read(self, frame_size: int) -> bytes:
        """
        Reads frame_size ms of audio from source.

        Returns:
            bytes of audio

        """
        ...

audio_complete: bool property

A property to tell the player if more audio is expected.

bitrate: Optional[int] class-attribute

Optionally specify a specific bitrate to encode this audio data with

encoder: Optional[Encoder] class-attribute

The encoder to use for this audio data

locked_stream: bool class-attribute

Prevents the audio task from closing automatically when no data is received.

needs_encode: bool class-attribute

Does this audio data need encoding with opus?

cleanup() abstractmethod

A method to optionally cleanup after this object is no longer required.

Source code in interactions/api/voice/audio.py
159
160
161
162
@abstractmethod
def cleanup(self) -> None:
    """A method to optionally cleanup after this object is no longer required."""
    ...

read(frame_size) abstractmethod

Reads frame_size ms of audio from source.

Returns:

Type Description
bytes

bytes of audio

Source code in interactions/api/voice/audio.py
169
170
171
172
173
174
175
176
177
178
@abstractmethod
def read(self, frame_size: int) -> bytes:
    """
    Reads frame_size ms of audio from source.

    Returns:
        bytes of audio

    """
    ...

RawInputAudio

Source code in interactions/api/voice/audio.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class RawInputAudio:
    decoded: bytes
    """The decoded audio"""
    pcm: bytes
    """The raw PCM audio"""
    sequence: int
    """The audio sequence"""
    audio_timestamp: int
    """The current timestamp for this audio"""
    timestamp_ns: float
    """The time this audio was received, in nanoseconds"""
    timestamp: float
    """The time this audio was received, in seconds"""
    ssrc: int
    """The source of this audio"""
    _recoder: "Recorder"
    """A reference to the audio recorder managing this object"""

    def __init__(self, recorder: "Recorder", data: bytes) -> None:
        self.decoded: bytes = b""
        self._recorder = recorder
        self.timestamp_ns = time.monotonic_ns()
        self.timestamp = self.timestamp_ns / 1e9
        self.pcm = b""

        self.ingest(data)

    def ingest(self, data: bytes) -> bytes | None:
        data = bytearray(data)
        header = data[:12]

        decrypted: bytes = self._recorder.decrypt(header, data[12:])
        self.ssrc = int.from_bytes(header[8:12], byteorder="big")
        self.sequence = int.from_bytes(header[2:4], byteorder="big")
        self.audio_timestamp = int.from_bytes(header[4:8], byteorder="big")

        if not self._recorder.recording_whitelist or self.user_id in self._recorder.recording_whitelist:
            # noinspection PyProtectedMember
            if decrypted[0] == 0xBE and decrypted[1] == 0xDE:
                # rtp header extension, remove it
                header_ext_length = int.from_bytes(decrypted[2:4], byteorder="big")
                decrypted = decrypted[4 + 4 * header_ext_length :]
            self.decoded = self._recorder.get_decoder(self.ssrc).decode(decrypted)
            return self.decoded

    @property
    def user_id(self) -> Optional[int]:
        """The ID of the user who made this audio."""
        while not self._recorder.state.ws.user_ssrc_map.get(self.ssrc):
            time.sleep(0.05)
        return self._recorder.state.ws.user_ssrc_map.get(self.ssrc)["user_id"]

audio_timestamp: int class-attribute

The current timestamp for this audio

sequence: int class-attribute

The audio sequence

ssrc: int class-attribute

The source of this audio

user_id: Optional[int] property

The ID of the user who made this audio.

Player

Bases: threading.Thread

Source code in interactions/api/voice/player.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
class Player(threading.Thread):
    def __init__(self, audio, v_state, loop) -> None:
        super().__init__()
        self.daemon = True

        self.current_audio: Optional[BaseAudio] = audio
        self.state: "ActiveVoiceState" = v_state
        self.loop: AbstractEventLoop = loop
        self.logger: Logger = self.state.ws.logger

        self._encoder: Encoder = Encoder()

        self._resume: threading.Event = threading.Event()

        self._stop_event: threading.Event = threading.Event()
        self._stopped: asyncio.Event = asyncio.Event()

        self._sent_payloads: int = 0

        self._cond = threading.Condition()

        if not shutil.which("ffmpeg"):
            raise RuntimeError(
                "Unable to start player. FFmpeg was not found. Please add it to your project directory or PATH. (https://ffmpeg.org/)"
            )
        ffmpeg_version = subprocess.check_output(["ffmpeg", "-version"], stderr=subprocess.DEVNULL)
        ffmpeg_version = ffmpeg_version.decode("utf-8").splitlines()[0].split(" ")[2]
        self.logger.debug(f"Detected ffmpeg version: {ffmpeg_version}")

    def __enter__(self) -> "Player":
        self.state.ws.cond = self._cond
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        with contextlib.suppress(AttributeError):
            self.state.ws.cond = None

    def stop(self) -> None:
        """Stop playing completely."""
        self._stop_event.set()
        with self._cond:
            self._cond.notify()

    def resume(self) -> None:
        """Resume playing."""
        self._resume.set()
        with self._cond:
            self._cond.notify()

    @property
    def paused(self) -> bool:
        """Is the player paused"""
        return not self._resume.is_set()

    def pause(self) -> None:
        """Pause the player."""
        self._resume.clear()

    @property
    def stopped(self) -> bool:
        """Is the player currently stopped?"""
        return self._stopped.is_set()

    @property
    def elapsed_time(self) -> float:
        """How many seconds of audio the player has sent."""
        return self._sent_payloads * self._encoder.delay

    def play(self) -> None:
        """Start playing."""
        self._stop_event.clear()
        self._resume.set()
        self.start()

    def run(self) -> None:
        """The main player loop to send audio to the voice websocket."""
        loops = 0

        if isinstance(self.current_audio, AudioVolume):
            # noinspection PyProtectedMember
            self.current_audio.volume = self.state._volume

        self.current_audio.encoder = self._encoder
        self._encoder.set_bitrate(getattr(self.current_audio, "bitrate", self.state.channel.bitrate))

        self._stopped.clear()

        asyncio.run_coroutine_threadsafe(self.state.ws.speaking(True), self.loop)
        self.logger.debug(f"Now playing {self.current_audio!r}")
        start = None

        try:
            while not self._stop_event.is_set():
                if not self.state.ws.ready.is_set() or not self._resume.is_set():
                    run_coroutine_threadsafe(self.state.ws.speaking(False), self.loop)
                    self.logger.debug("Voice playback has been suspended!")

                    wait_for = []

                    if not self.state.ws.ready.is_set():
                        wait_for.append(self.state.ws.ready)
                    if not self._resume.is_set():
                        wait_for.append(self._resume)

                    with self._cond:
                        while not (self._stop_event.is_set() or all(x.is_set() for x in wait_for)):
                            self._cond.wait()
                    if self._stop_event.is_set():
                        continue

                    run_coroutine_threadsafe(self.state.ws.speaking(), self.loop)
                    self.logger.debug("Voice playback has been resumed!")
                    start = None
                    loops = 0

                if data := self.current_audio.read(self._encoder.frame_size):
                    self.state.ws.send_packet(data, self._encoder, needs_encode=self.current_audio.needs_encode)
                elif self.current_audio.locked_stream or not self.current_audio.audio_complete:
                    # if more audio is expected
                    self.state.ws.send_packet(b"\xF8\xFF\xFE", self._encoder, needs_encode=False)
                else:
                    break

                if not start:
                    start = perf_counter()

                loops += 1
                self._sent_payloads += 1  # used for duration calc
                sleep(max(0.0, start + (self._encoder.delay * loops) - perf_counter()))
        finally:
            asyncio.run_coroutine_threadsafe(self.state.ws.speaking(False), self.loop)
            self.current_audio.cleanup()
            self.loop.call_soon_threadsafe(self._stopped.set)

elapsed_time: float property

How many seconds of audio the player has sent.

paused: bool property

Is the player paused

stopped: bool property

Is the player currently stopped?

pause()

Pause the player.

Source code in interactions/api/voice/player.py
73
74
75
def pause(self) -> None:
    """Pause the player."""
    self._resume.clear()

play()

Start playing.

Source code in interactions/api/voice/player.py
87
88
89
90
91
def play(self) -> None:
    """Start playing."""
    self._stop_event.clear()
    self._resume.set()
    self.start()

resume()

Resume playing.

Source code in interactions/api/voice/player.py
62
63
64
65
66
def resume(self) -> None:
    """Resume playing."""
    self._resume.set()
    with self._cond:
        self._cond.notify()

run()

The main player loop to send audio to the voice websocket.

Source code in interactions/api/voice/player.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
def run(self) -> None:
    """The main player loop to send audio to the voice websocket."""
    loops = 0

    if isinstance(self.current_audio, AudioVolume):
        # noinspection PyProtectedMember
        self.current_audio.volume = self.state._volume

    self.current_audio.encoder = self._encoder
    self._encoder.set_bitrate(getattr(self.current_audio, "bitrate", self.state.channel.bitrate))

    self._stopped.clear()

    asyncio.run_coroutine_threadsafe(self.state.ws.speaking(True), self.loop)
    self.logger.debug(f"Now playing {self.current_audio!r}")
    start = None

    try:
        while not self._stop_event.is_set():
            if not self.state.ws.ready.is_set() or not self._resume.is_set():
                run_coroutine_threadsafe(self.state.ws.speaking(False), self.loop)
                self.logger.debug("Voice playback has been suspended!")

                wait_for = []

                if not self.state.ws.ready.is_set():
                    wait_for.append(self.state.ws.ready)
                if not self._resume.is_set():
                    wait_for.append(self._resume)

                with self._cond:
                    while not (self._stop_event.is_set() or all(x.is_set() for x in wait_for)):
                        self._cond.wait()
                if self._stop_event.is_set():
                    continue

                run_coroutine_threadsafe(self.state.ws.speaking(), self.loop)
                self.logger.debug("Voice playback has been resumed!")
                start = None
                loops = 0

            if data := self.current_audio.read(self._encoder.frame_size):
                self.state.ws.send_packet(data, self._encoder, needs_encode=self.current_audio.needs_encode)
            elif self.current_audio.locked_stream or not self.current_audio.audio_complete:
                # if more audio is expected
                self.state.ws.send_packet(b"\xF8\xFF\xFE", self._encoder, needs_encode=False)
            else:
                break

            if not start:
                start = perf_counter()

            loops += 1
            self._sent_payloads += 1  # used for duration calc
            sleep(max(0.0, start + (self._encoder.delay * loops) - perf_counter()))
    finally:
        asyncio.run_coroutine_threadsafe(self.state.ws.speaking(False), self.loop)
        self.current_audio.cleanup()
        self.loop.call_soon_threadsafe(self._stopped.set)

stop()

Stop playing completely.

Source code in interactions/api/voice/player.py
56
57
58
59
60
def stop(self) -> None:
    """Stop playing completely."""
    self._stop_event.set()
    with self._cond:
        self._cond.notify()

Errors

AlreadyDeferred

Bases: InteractionException

An interaction was already deferred, and you attempted to defer it again.

Source code in interactions/client/errors.py
389
390
class AlreadyDeferred(InteractionException):
    """An interaction was already deferred, and you attempted to defer it again."""

AlreadyResponded

Bases: AlreadyDeferred

An interaction was already responded to, and you attempted to defer it

Source code in interactions/client/errors.py
393
394
class AlreadyResponded(AlreadyDeferred):
    """An interaction was already responded to, and you attempted to defer it"""

BadArgument

Bases: CommandException

A prefixed command encountered an invalid argument.

Source code in interactions/client/errors.py
323
324
325
326
327
328
329
330
331
class BadArgument(CommandException):
    """A prefixed command encountered an invalid argument."""

    def __init__(self, message: Optional[str] = None, *args: Any) -> None:
        if message is not None:
            message = escape_mentions(message)
            super().__init__(message, *args)
        else:
            super().__init__(*args)

BadRequest

Bases: HTTPException

A bad request was made.

Source code in interactions/client/errors.py
180
181
class BadRequest(HTTPException):
    """A bad request was made."""

BotException

Bases: LibraryException

An issue occurred in the client, likely user error.

Source code in interactions/client/errors.py
57
58
class BotException(LibraryException):
    """An issue occurred in the client, likely user error."""

CommandCheckFailure

Bases: CommandException

A command check failed.

Attributes:

Name Type Description
command BaseCommand

The command that's check failed

check Callable[..., Coroutine]

The check that failed

Source code in interactions/client/errors.py
307
308
309
310
311
312
313
314
315
316
317
318
319
320
class CommandCheckFailure(CommandException):
    """
    A command check failed.

    Attributes:
        command BaseCommand: The command that's check failed
        check Callable[..., Coroutine]: The check that failed

    """

    def __init__(self, command: "BaseCommand", check: Callable[..., Coroutine], context: "BaseContext") -> None:
        self.command: "BaseCommand" = command
        self.check: Callable[..., Coroutine] = check
        self.ctx = context

CommandException

Bases: BotException

An error occurred trying to execute a command.

Source code in interactions/client/errors.py
276
277
class CommandException(BotException):
    """An error occurred trying to execute a command."""

CommandOnCooldown

Bases: CommandException

A command is on cooldown, and was attempted to be executed.

Attributes:

Name Type Description
command BaseCommand

The command that is on cooldown

cooldown CooldownSystem

The cooldown system

Source code in interactions/client/errors.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
class CommandOnCooldown(CommandException):
    """
    A command is on cooldown, and was attempted to be executed.

    Attributes:
        command BaseCommand: The command that is on cooldown
        cooldown CooldownSystem: The cooldown system

    """

    def __init__(self, command: "BaseCommand", cooldown: "CooldownSystem") -> None:
        self.command: "BaseCommand" = command
        self.cooldown: "CooldownSystem" = cooldown

        super().__init__(f"Command on cooldown... {cooldown.get_cooldown_time():.2f} seconds until reset")

DiscordError

Bases: HTTPException

A discord-side error.

Source code in interactions/client/errors.py
176
177
class DiscordError(HTTPException):
    """A discord-side error."""

EmptyMessageException

Bases: MessageException

You have attempted to send a message without any content or embeds

Source code in interactions/client/errors.py
338
339
class EmptyMessageException(MessageException):
    """You have attempted to send a message without any content or embeds"""

EphemeralEditException

Bases: MessageException

Your bot attempted to edit an ephemeral message. This is not possible.

Its worth noting you can edit an ephemeral message with component's edit_origin method.

Source code in interactions/client/errors.py
342
343
344
345
346
347
348
349
350
351
352
353
354
class EphemeralEditException(MessageException):
    """
    Your bot attempted to edit an ephemeral message. This is not possible.

    Its worth noting you can edit an ephemeral message with component's
    `edit_origin` method.

    """

    def __init__(self) -> None:
        super().__init__(
            "Ephemeral messages can only be edited with component's `edit_origin` method or using InteractionContext"
        )

EventLocationNotProvided

Bases: BotException

Raised when you have entity_type external and no location is provided.

Source code in interactions/client/errors.py
401
402
class EventLocationNotProvided(BotException):
    """Raised when you have entity_type external and no location is provided."""

ExtensionException

Bases: BotException

An error occurred with an extension.

Source code in interactions/client/errors.py
264
265
class ExtensionException(BotException):
    """An error occurred with an extension."""

ExtensionLoadException

Bases: ExtensionException

An error occurred loading an extension.

Source code in interactions/client/errors.py
272
273
class ExtensionLoadException(ExtensionException):
    """An error occurred loading an extension."""

ExtensionNotFound

Bases: ExtensionException

The desired extension was not found.

Source code in interactions/client/errors.py
268
269
class ExtensionNotFound(ExtensionException):
    """The desired extension was not found."""

Forbidden

Bases: HTTPException

You do not have access to this.

Source code in interactions/client/errors.py
184
185
class Forbidden(HTTPException):
    """You do not have access to this."""

ForeignWebhookException

Bases: LibraryException

Raised when you attempt to send using a webhook you did not create.

Source code in interactions/client/errors.py
397
398
class ForeignWebhookException(LibraryException):
    """Raised when you attempt to send using a webhook you did not create."""

GatewayNotFound

Bases: LibraryException

An exception that is raised when the gateway for Discord could not be found.

Source code in interactions/client/errors.py
61
62
63
64
65
class GatewayNotFound(LibraryException):
    """An exception that is raised when the gateway for Discord could not be found."""

    def __init__(self) -> None:
        super().__init__("Unable to find discord gateway!")

HTTPException

Bases: LibraryException

A HTTP request resulted in an exception.

Attributes:

Name Type Description
response aiohttp.ClientResponse

The response of the HTTP request

text str

The text of the exception, could be None

status int

The HTTP status code

code int

The discord error code, if one is provided

route Route

The HTTP route that was used

Source code in interactions/client/errors.py
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
class HTTPException(LibraryException):
    """
    A HTTP request resulted in an exception.

    Attributes:
        response aiohttp.ClientResponse: The response of the HTTP request
        text str: The text of the exception, could be None
        status int: The HTTP status code
        code int: The discord error code, if one is provided
        route Route: The HTTP route that was used

    """

    def __init__(
        self,
        response: aiohttp.ClientResponse,
        text: const.Absent[str] = const.MISSING,
        discord_code: const.Absent[int] = const.MISSING,
        **kwargs,
    ) -> None:
        self.response: aiohttp.ClientResponse = response
        self.status: int = response.status
        self.code: const.Absent[int] = discord_code
        self.text: const.Absent[str] = text
        self.errors: const.Absent[Any] = const.MISSING
        self.route = kwargs.get("route", const.MISSING)

        if data := kwargs.get("response_data"):
            if isinstance(data, dict):
                self.text = data.get("message", const.MISSING)
                self.code = data.get("code", const.MISSING)
                self.errors = data.get("errors", const.MISSING)
            else:
                self.text = data
        super().__init__(f"{self.status}|{self.response.reason}: {f'({self.code}) ' if self.code else ''}{self.text}")

    def __str__(self) -> str:
        if not self.errors:
            return f"HTTPException: {self.status}|{self.response.reason} || {self.text}"
        try:
            errors = self.search_for_message(self.errors)
        except (KeyError, ValueError, TypeError):
            errors = [self.text]
        return f"HTTPException: {self.status}|{self.response.reason}: " + "\n".join(errors)

    def __repr__(self) -> str:
        return str(self)

    @staticmethod
    def search_for_message(errors: dict, lookup: Optional[dict] = None) -> list[str]:
        """
        Search the exceptions error dictionary for a message explaining the issue.

        Args:
            errors: The error dictionary of the http exception
            lookup: A lookup dictionary to use to convert indexes into named items

        Returns:
            A list of parsed error strings found

        """
        messages: List[str] = []
        errors = errors.get("errors", errors)

        def maybe_int(x: SupportsInt | Any) -> Union[int, Any]:
            """If something can be an integer, convert it to one, otherwise return its normal value"""
            try:
                return int(x)
            except ValueError:
                return x

        def _parse(_errors: dict, keys: Optional[List[str]] = None) -> None:
            """Search through the entire dictionary for any errors defined"""
            for key, val in _errors.items():
                if key == "_errors":
                    key_out = []
                    if keys:
                        if lookup:
                            # this code simply substitutes keys for attribute names
                            _lookup = lookup
                            for _key in keys:
                                _lookup = _lookup[maybe_int(_key)]

                                if isinstance(_lookup, dict):
                                    key_out.append(_lookup.get("name", _key))
                                else:
                                    key_out.append(_key)
                        else:
                            key_out = keys

                    for msg in val:
                        messages.append(f"{'->'.join(key_out)} {msg['code']}: {msg['message']}")
                else:
                    if keys:
                        keys.append(key)
                    else:
                        keys = [key]
                    _parse(val, keys)

        _parse(errors)

        return messages

search_for_message(errors, lookup=None) staticmethod

Search the exceptions error dictionary for a message explaining the issue.

Parameters:

Name Type Description Default
errors dict

The error dictionary of the http exception

required
lookup Optional[dict]

A lookup dictionary to use to convert indexes into named items

None

Returns:

Type Description
list[str]

A list of parsed error strings found

Source code in interactions/client/errors.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
@staticmethod
def search_for_message(errors: dict, lookup: Optional[dict] = None) -> list[str]:
    """
    Search the exceptions error dictionary for a message explaining the issue.

    Args:
        errors: The error dictionary of the http exception
        lookup: A lookup dictionary to use to convert indexes into named items

    Returns:
        A list of parsed error strings found

    """
    messages: List[str] = []
    errors = errors.get("errors", errors)

    def maybe_int(x: SupportsInt | Any) -> Union[int, Any]:
        """If something can be an integer, convert it to one, otherwise return its normal value"""
        try:
            return int(x)
        except ValueError:
            return x

    def _parse(_errors: dict, keys: Optional[List[str]] = None) -> None:
        """Search through the entire dictionary for any errors defined"""
        for key, val in _errors.items():
            if key == "_errors":
                key_out = []
                if keys:
                    if lookup:
                        # this code simply substitutes keys for attribute names
                        _lookup = lookup
                        for _key in keys:
                            _lookup = _lookup[maybe_int(_key)]

                            if isinstance(_lookup, dict):
                                key_out.append(_lookup.get("name", _key))
                            else:
                                key_out.append(_key)
                    else:
                        key_out = keys

                for msg in val:
                    messages.append(f"{'->'.join(key_out)} {msg['code']}: {msg['message']}")
            else:
                if keys:
                    keys.append(key)
                else:
                    keys = [key]
                _parse(val, keys)

    _parse(errors)

    return messages

InteractionException

Bases: BotException

An error occurred with an interaction.

Source code in interactions/client/errors.py
368
369
class InteractionException(BotException):
    """An error occurred with an interaction."""

InteractionMissingAccess

Bases: InteractionException

The bot does not have access to the specified scope.

Source code in interactions/client/errors.py
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
class InteractionMissingAccess(InteractionException):
    """The bot does not have access to the specified scope."""

    def __init__(self, scope: "Snowflake_Type") -> None:
        self.scope: "Snowflake_Type" = scope

        if scope == const.GLOBAL_SCOPE:
            err_msg = "Unable to sync commands global commands"
        else:
            err_msg = (
                f"Unable to sync commands for guild `{scope}` -- Ensure the bot properly added to that guild "
                f"with `application.commands` scope. "
            )

        super().__init__(err_msg)

LibraryException

Bases: Exception

Base Exception of i.py.

Source code in interactions/client/errors.py
53
54
class LibraryException(Exception):
    """Base Exception of i.py."""

LoginError

Bases: BotException

The bot failed to login, check your token.

Source code in interactions/client/errors.py
68
69
class LoginError(BotException):
    """The bot failed to login, check your token."""

MaxConcurrencyReached

Bases: CommandException

A command has exhausted the max concurrent requests.

Source code in interactions/client/errors.py
297
298
299
300
301
302
303
304
class MaxConcurrencyReached(CommandException):
    """A command has exhausted the max concurrent requests."""

    def __init__(self, command: "BaseCommand", max_conc: "MaxConcurrency") -> None:
        self.command: "BaseCommand" = command
        self.max_conc: "MaxConcurrency" = max_conc

        super().__init__(f"Command has exhausted the max concurrent requests. ({max_conc.concurrent} simultaneously)")

MessageException

Bases: BotException

A message operation encountered an exception.

Source code in interactions/client/errors.py
334
335
class MessageException(BotException):
    """A message operation encountered an exception."""

NotFound

Bases: HTTPException

This resource could not be found.

Source code in interactions/client/errors.py
188
189
class NotFound(HTTPException):
    """This resource could not be found."""

RateLimited

Bases: HTTPException

Discord is rate limiting this application.

Source code in interactions/client/errors.py
192
193
class RateLimited(HTTPException):
    """Discord is rate limiting this application."""

ThreadException

Bases: BotException

A thread operation encountered an exception.

Source code in interactions/client/errors.py
357
358
class ThreadException(BotException):
    """A thread operation encountered an exception."""

ThreadOutsideOfGuild

Bases: ThreadException

A thread was attempted to be created outside of a guild.

Source code in interactions/client/errors.py
361
362
363
364
365
class ThreadOutsideOfGuild(ThreadException):
    """A thread was attempted to be created outside of a guild."""

    def __init__(self) -> None:
        super().__init__("Threads cannot be created outside of guilds")

TooManyChanges

Bases: LibraryException

You have changed something too frequently.

Source code in interactions/client/errors.py
196
197
class TooManyChanges(LibraryException):
    """You have changed something too frequently."""

VoiceAlreadyConnected

Bases: BotException

Raised when you attempt to connect a voice channel that is already connected.

Source code in interactions/client/errors.py
405
406
407
408
409
class VoiceAlreadyConnected(BotException):
    """Raised when you attempt to connect a voice channel that is already connected."""

    def __init__(self) -> None:
        super().__init__("Bot already connected to the voice channel")

VoiceConnectionTimeout

Bases: LibraryException

Raised when the bot fails to connect to a voice channel.

Source code in interactions/client/errors.py
419
420
421
422
423
class VoiceConnectionTimeout(LibraryException):
    """Raised when the bot fails to connect to a voice channel."""

    def __init__(self) -> None:
        super().__init__("Failed to connect to voice channel. Did not receive a response from Discord")

VoiceNotConnected

Bases: BotException

Raised when you attempt to connect a voice channel that is not connected.

Source code in interactions/client/errors.py
412
413
414
415
416
class VoiceNotConnected(BotException):
    """Raised when you attempt to connect a voice channel that is not connected."""

    def __init__(self) -> None:
        super().__init__("Bot is not connected to any voice channels in given guild")

VoiceWebSocketClosed

Bases: LibraryException

The voice websocket was closed.

Source code in interactions/client/errors.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
class VoiceWebSocketClosed(LibraryException):
    """The voice websocket was closed."""

    code: int = 0
    codes: ClassVar[Dict[int, str]] = {
        1000: "Normal Closure",
        4000: "Unknown Error",
        4001: "Unknown OpCode",
        4002: "Decode Error",
        4003: "Not Authenticated",
        4004: "Authentication Failed",
        4005: "Already Authenticated",
        4006: "Session no longer valid",
        4007: "Invalid seq",
        4009: "Session Timed Out",
        4011: "Server not found",
        4012: "Unknown protocol",
        4014: "Disconnected",
        4015: "Voice Server Crashed",
        4016: "Unknown encryption mode",
    }

    def __init__(self, code: int) -> None:
        self.code = code
        super().__init__(f"The Websocket closed with code: {code} - {self.codes.get(code, 'Unknown Error')}")

WebSocketClosed

Bases: LibraryException

The websocket was closed.

Source code in interactions/client/errors.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
class WebSocketClosed(LibraryException):
    """The websocket was closed."""

    code: int = 0
    codes: ClassVar[Dict[int, str]] = {
        1000: "Normal Closure",
        4000: "Unknown Error",
        4001: "Unknown OpCode",
        4002: "Decode Error",
        4003: "Not Authenticated",
        4004: "Authentication Failed",
        4005: "Already Authenticated",
        4007: "Invalid seq",
        4008: "Rate limited",
        4009: "Session Timed Out",
        4010: "Invalid Shard",
        4011: "Sharding Required",
        4012: "Invalid API Version",
        4013: "Invalid Intents",
        4014: "Disallowed Intents",
    }

    def __init__(self, code: int) -> None:
        self.code = code
        super().__init__(f"The Websocket closed with code: {code} - {self.codes.get(code, 'Unknown Error')}")

WebSocketRestart

Bases: LibraryException

The websocket closed, and is safe to restart.

Source code in interactions/client/errors.py
254
255
256
257
258
259
260
261
class WebSocketRestart(LibraryException):
    """The websocket closed, and is safe to restart."""

    resume: bool = False

    def __init__(self, resume: bool = False) -> None:
        self.resume = resume
        super().__init__("Websocket connection closed... reconnecting")

Events

These are events dispatched by Discord. This is intended as a reference so you know what data to expect for each event.

Example Usage

To listen to an event, use the listen decorator:

1
2
3
4
5
6
from interactions import listen
from interactions.api.events import ChannelCreate  # or any other event

@listen(ChannelCreate)
async def an_event_handler(event: ChannelCreate):
    print(f"Channel created with name: {event.channel.name}")

For more information, including other ways to listen to events, see the events guide.

Warning

While all of these events are documented, not all of them are used, currently.

AutoModCreated

Bases: BaseEvent

Dispatched when an auto mod rule is created

Source code in interactions/api/events/discord.py
141
142
143
144
145
146
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class AutoModCreated(BaseEvent):
    """Dispatched when an auto mod rule is created"""

    guild: "Guild" = attrs.field(repr=False, metadata=docs("The guild the rule was modified in"))
    rule: "AutoModRule" = attrs.field(repr=False, metadata=docs("The rule that was modified"))

AutoModDeleted

Bases: AutoModCreated

Dispatched when an auto mod rule is deleted

Source code in interactions/api/events/discord.py
156
157
158
159
160
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class AutoModDeleted(AutoModCreated):
    """Dispatched when an auto mod rule is deleted"""

    ...

AutoModExec

Bases: BaseEvent

Dispatched when an auto modation action is executed

Source code in interactions/api/events/discord.py
132
133
134
135
136
137
138
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class AutoModExec(BaseEvent):
    """Dispatched when an auto modation action is executed"""

    execution: "AutoModerationAction" = attrs.field(repr=False, metadata=docs("The executed auto mod action"))
    channel: "TYPE_ALL_CHANNEL" = attrs.field(repr=False, metadata=docs("The channel the action was executed in"))
    guild: "Guild" = attrs.field(repr=False, metadata=docs("The guild the action was executed in"))

AutoModUpdated

Bases: AutoModCreated

Dispatched when an auto mod rule is modified

Source code in interactions/api/events/discord.py
149
150
151
152
153
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class AutoModUpdated(AutoModCreated):
    """Dispatched when an auto mod rule is modified"""

    ...

BanCreate

Bases: GuildEvent

Dispatched when someone was banned from a guild.

Source code in interactions/api/events/discord.py
338
339
340
341
342
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class BanCreate(GuildEvent):
    """Dispatched when someone was banned from a guild."""

    user: "BaseUser" = attrs.field(repr=False, metadata=docs("The user"))

BanRemove

Bases: BanCreate

Dispatched when a users ban is removed.

Source code in interactions/api/events/discord.py
345
346
347
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class BanRemove(BanCreate):
    """Dispatched when a users ban is removed."""

BaseMessagePollEvent

Bases: BaseEvent

Source code in interactions/api/events/discord.py
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class BaseMessagePollEvent(BaseEvent):
    user_id: "Snowflake_Type" = attrs.field(repr=False)
    """The ID of the user that voted"""
    channel_id: "Snowflake_Type" = attrs.field(repr=False)
    """The ID of the channel the poll is in"""
    message_id: "Snowflake_Type" = attrs.field(repr=False)
    """The ID of the message the poll is in"""
    answer_id: int = attrs.field(repr=False)
    """The ID of the answer the user voted for"""
    guild_id: "Optional[Snowflake_Type]" = attrs.field(repr=False, default=None)
    """The ID of the guild the poll is in"""

    def get_message(self) -> "Optional[Message]":
        """Get the message object if it is cached"""
        return self.client.cache.get_message(self.channel_id, self.message_id)

    def get_user(self) -> "Optional[User]":
        """Get the user object if it is cached"""
        return self.client.get_user(self.user_id)

    def get_channel(self) -> "Optional[TYPE_ALL_CHANNEL]":
        """Get the channel object if it is cached"""
        return self.client.get_channel(self.channel_id)

    def get_guild(self) -> "Optional[Guild]":
        """Get the guild object if it is cached"""
        return self.client.get_guild(self.guild_id) if self.guild_id is not None else None

    def get_poll(self) -> "Optional[Poll]":
        """Get the poll object if it is cached"""
        message = self.get_message()
        return message.poll if message is not None else None

    async def fetch_message(self) -> "Message":
        """Fetch the message the poll is in"""
        return await self.client.cache.fetch_message(self.channel_id, self.message_id)

    async def fetch_user(self) -> "User":
        """Fetch the user that voted"""
        return await self.client.fetch_user(self.user_id)

    async def fetch_channel(self) -> "TYPE_ALL_CHANNEL":
        """Fetch the channel the poll is in"""
        return await self.client.fetch_channel(self.channel_id)

    async def fetch_guild(self) -> "Optional[Guild]":
        """Fetch the guild the poll is in"""
        return await self.client.fetch_guild(self.guild_id) if self.guild_id is not None else None

    async def fetch_poll(self) -> "Poll":
        """Fetch the poll object"""
        message = await self.fetch_message()
        return message.poll

answer_id: int = attrs.field(repr=False) class-attribute

The ID of the answer the user voted for

channel_id: Snowflake_Type = attrs.field(repr=False) class-attribute

The ID of the channel the poll is in

guild_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) class-attribute

The ID of the guild the poll is in

message_id: Snowflake_Type = attrs.field(repr=False) class-attribute

The ID of the message the poll is in

user_id: Snowflake_Type = attrs.field(repr=False) class-attribute

The ID of the user that voted

fetch_channel() async

Fetch the channel the poll is in

Source code in interactions/api/events/discord.py
636
637
638
async def fetch_channel(self) -> "TYPE_ALL_CHANNEL":
    """Fetch the channel the poll is in"""
    return await self.client.fetch_channel(self.channel_id)

fetch_guild() async

Fetch the guild the poll is in

Source code in interactions/api/events/discord.py
640
641
642
async def fetch_guild(self) -> "Optional[Guild]":
    """Fetch the guild the poll is in"""
    return await self.client.fetch_guild(self.guild_id) if self.guild_id is not None else None

fetch_message() async

Fetch the message the poll is in

Source code in interactions/api/events/discord.py
628
629
630
async def fetch_message(self) -> "Message":
    """Fetch the message the poll is in"""
    return await self.client.cache.fetch_message(self.channel_id, self.message_id)

fetch_poll() async

Fetch the poll object

Source code in interactions/api/events/discord.py
644
645
646
647
async def fetch_poll(self) -> "Poll":
    """Fetch the poll object"""
    message = await self.fetch_message()
    return message.poll

fetch_user() async

Fetch the user that voted

Source code in interactions/api/events/discord.py
632
633
634
async def fetch_user(self) -> "User":
    """Fetch the user that voted"""
    return await self.client.fetch_user(self.user_id)

get_channel()

Get the channel object if it is cached

Source code in interactions/api/events/discord.py
615
616
617
def get_channel(self) -> "Optional[TYPE_ALL_CHANNEL]":
    """Get the channel object if it is cached"""
    return self.client.get_channel(self.channel_id)

get_guild()

Get the guild object if it is cached

Source code in interactions/api/events/discord.py
619
620
621
def get_guild(self) -> "Optional[Guild]":
    """Get the guild object if it is cached"""
    return self.client.get_guild(self.guild_id) if self.guild_id is not None else None

get_message()

Get the message object if it is cached

Source code in interactions/api/events/discord.py
607
608
609
def get_message(self) -> "Optional[Message]":
    """Get the message object if it is cached"""
    return self.client.cache.get_message(self.channel_id, self.message_id)

get_poll()

Get the poll object if it is cached

Source code in interactions/api/events/discord.py
623
624
625
626
def get_poll(self) -> "Optional[Poll]":
    """Get the poll object if it is cached"""
    message = self.get_message()
    return message.poll if message is not None else None

get_user()

Get the user object if it is cached

Source code in interactions/api/events/discord.py
611
612
613
def get_user(self) -> "Optional[User]":
    """Get the user object if it is cached"""
    return self.client.get_user(self.user_id)

BaseVoiceEvent

Bases: BaseEvent

Source code in interactions/api/events/discord.py
759
760
761
762
763
764
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class BaseVoiceEvent(BaseEvent):
    state: "VoiceState" = attrs.field(
        repr=False,
    )
    """The current voice state of the user"""

state: VoiceState = attrs.field(repr=False) class-attribute

The current voice state of the user

ChannelCreate

Bases: BaseEvent

Dispatched when a channel is created.

Source code in interactions/api/events/discord.py
177
178
179
180
181
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ChannelCreate(BaseEvent):
    """Dispatched when a channel is created."""

    channel: "TYPE_ALL_CHANNEL" = attrs.field(repr=False, metadata=docs("The channel this event is dispatched from"))

ChannelDelete

Bases: ChannelCreate

Dispatched when a channel is deleted.

Source code in interactions/api/events/discord.py
198
199
200
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ChannelDelete(ChannelCreate):
    """Dispatched when a channel is deleted."""

ChannelPinsUpdate

Bases: ChannelCreate

Dispatched when a channel's pins are updated.

Source code in interactions/api/events/discord.py
203
204
205
206
207
208
209
210
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ChannelPinsUpdate(ChannelCreate):
    """Dispatched when a channel's pins are updated."""

    last_pin_timestamp: "Timestamp" = attrs.field(
        repr=False,
    )
    """The time at which the most recent pinned message was pinned"""

last_pin_timestamp: Timestamp = attrs.field(repr=False) class-attribute

The time at which the most recent pinned message was pinned

ChannelUpdate

Bases: BaseEvent

Dispatched when a channel is updated.

Source code in interactions/api/events/discord.py
184
185
186
187
188
189
190
191
192
193
194
195
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ChannelUpdate(BaseEvent):
    """Dispatched when a channel is updated."""

    before: "TYPE_ALL_CHANNEL" = attrs.field(
        repr=False,
    )
    """Channel before this event. MISSING if it was not cached before"""
    after: "TYPE_ALL_CHANNEL" = attrs.field(
        repr=False,
    )
    """Channel after this event"""

after: TYPE_ALL_CHANNEL = attrs.field(repr=False) class-attribute

Channel after this event

before: TYPE_ALL_CHANNEL = attrs.field(repr=False) class-attribute

Channel before this event. MISSING if it was not cached before

EntitlementCreate

Bases: BaseEntitlementEvent

Dispatched when a user subscribes to a SKU.

Source code in interactions/api/events/discord.py
915
916
917
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class EntitlementCreate(BaseEntitlementEvent):
    """Dispatched when a user subscribes to a SKU."""

EntitlementDelete

Bases: BaseEntitlementEvent

Dispatched when a user's entitlement is deleted.

Notably, this event is not dispatched when a user's subscription is cancelled. Instead, you simply won't receive an EntitlementUpdate event for the next billing period.

Source code in interactions/api/events/discord.py
925
926
927
928
929
930
931
932
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class EntitlementDelete(BaseEntitlementEvent):
    """
    Dispatched when a user's entitlement is deleted.

    Notably, this event is not dispatched when a user's subscription is cancelled.
    Instead, you simply won't receive an EntitlementUpdate event for the next billing period.
    """

EntitlementUpdate

Bases: BaseEntitlementEvent

Dispatched when a user's subscription renews for the next billing period.

Source code in interactions/api/events/discord.py
920
921
922
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class EntitlementUpdate(BaseEntitlementEvent):
    """Dispatched when a user's subscription renews for the next billing period."""

GuildAuditLogEntryCreate

Bases: GuildEvent

Dispatched when audit log entry is created

Source code in interactions/api/events/discord.py
849
850
851
852
853
854
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildAuditLogEntryCreate(GuildEvent):
    """Dispatched when audit log entry is created"""

    audit_log_entry: interactions.models.AuditLogEntry = attrs.field(repr=False)
    """The audit log entry object"""

audit_log_entry: interactions.models.AuditLogEntry = attrs.field(repr=False) class-attribute

The audit log entry object

GuildAvailable

Bases: GuildEvent

Dispatched when a guild becomes available.

Source code in interactions/api/events/discord.py
328
329
330
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildAvailable(GuildEvent):
    """Dispatched when a guild becomes available."""

GuildEmojisUpdate

Bases: GuildEvent

Dispatched when a guild's emojis are updated.

Source code in interactions/api/events/discord.py
350
351
352
353
354
355
356
357
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildEmojisUpdate(GuildEvent):
    """Dispatched when a guild's emojis are updated."""

    before: List["CustomEmoji"] = attrs.field(repr=False, factory=list)
    """List of emoji before this event. Only includes emojis that were cached. To enable the emoji cache (and this field), start your bot with `Client(enable_emoji_cache=True)`"""
    after: List["CustomEmoji"] = attrs.field(repr=False, factory=list)
    """List of emoji after this event"""

after: List[CustomEmoji] = attrs.field(repr=False, factory=list) class-attribute

List of emoji after this event

before: List[CustomEmoji] = attrs.field(repr=False, factory=list) class-attribute

List of emoji before this event. Only includes emojis that were cached. To enable the emoji cache (and this field), start your bot with Client(enable_emoji_cache=True)

GuildJoin

Bases: GuildEvent

Dispatched when a guild is joined, created, or becomes available.

Note

This is called multiple times during startup, check the bot is ready before responding to this.

Source code in interactions/api/events/discord.py
292
293
294
295
296
297
298
299
300
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildJoin(GuildEvent):
    """
    Dispatched when a guild is joined, created, or becomes available.

    !!! note
        This is called multiple times during startup, check the bot is ready before responding to this.

    """

GuildLeft

Bases: BaseEvent

Dispatched when a guild is left.

Source code in interactions/api/events/discord.py
317
318
319
320
321
322
323
324
325
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildLeft(BaseEvent):
    """Dispatched when a guild is left."""

    guild_id: "Snowflake_Type" = attrs.field(repr=True, converter=to_snowflake)
    """The ID of the guild"""

    guild: "Guild" = attrs.field(repr=True)
    """The guild this event is dispatched from"""

guild: Guild = attrs.field(repr=True) class-attribute

The guild this event is dispatched from

guild_id: Snowflake_Type = attrs.field(repr=True, converter=to_snowflake) class-attribute

The ID of the guild

GuildMembersChunk

Bases: GuildEvent

Sent in response to Guild Request Members.

You can use the chunk_index and chunk_count to calculate how many chunks are left for your request.

Source code in interactions/api/events/discord.py
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildMembersChunk(GuildEvent):
    """
    Sent in response to Guild Request Members.

    You can use the `chunk_index` and `chunk_count` to calculate how
    many chunks are left for your request.

    """

    chunk_index: int = attrs.field(
        repr=False,
    )
    """The chunk index in the expected chunks for this response (0 <= chunk_index < chunk_count)"""
    chunk_count: int = attrs.field(
        repr=False,
    )
    """the total number of expected chunks for this response"""
    presences: List = attrs.field(
        repr=False,
    )
    """if passing true to `REQUEST_GUILD_MEMBERS`, presences of the returned members will be here"""
    nonce: str = attrs.field(
        repr=False,
    )
    """The nonce used in the request, if any"""
    members: List["Member"] = attrs.field(repr=False, factory=list)
    """A list of members"""

chunk_count: int = attrs.field(repr=False) class-attribute

the total number of expected chunks for this response

chunk_index: int = attrs.field(repr=False) class-attribute

The chunk index in the expected chunks for this response (0 <= chunk_index < chunk_count)

members: List[Member] = attrs.field(repr=False, factory=list) class-attribute

A list of members

nonce: str = attrs.field(repr=False) class-attribute

The nonce used in the request, if any

presences: List = attrs.field(repr=False) class-attribute

if passing true to REQUEST_GUILD_MEMBERS, presences of the returned members will be here

GuildScheduledEventCreate

Bases: BaseEvent

Dispatched when scheduled event is created

Source code in interactions/api/events/discord.py
857
858
859
860
861
862
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildScheduledEventCreate(BaseEvent):
    """Dispatched when scheduled event is created"""

    scheduled_event: "ScheduledEvent" = attrs.field(repr=True)
    """The scheduled event object"""

scheduled_event: ScheduledEvent = attrs.field(repr=True) class-attribute

The scheduled event object

GuildScheduledEventDelete

Bases: GuildScheduledEventCreate

Dispatched when scheduled event is deleted

Source code in interactions/api/events/discord.py
875
876
877
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildScheduledEventDelete(GuildScheduledEventCreate):
    """Dispatched when scheduled event is deleted"""

GuildScheduledEventUpdate

Bases: BaseEvent

Dispatched when scheduled event is updated

Source code in interactions/api/events/discord.py
865
866
867
868
869
870
871
872
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildScheduledEventUpdate(BaseEvent):
    """Dispatched when scheduled event is updated"""

    before: Absent["ScheduledEvent"] = attrs.field(repr=True)
    """The scheduled event before this event was created"""
    after: "ScheduledEvent" = attrs.field(repr=True)
    """The scheduled event after this event was created"""

after: ScheduledEvent = attrs.field(repr=True) class-attribute

The scheduled event after this event was created

before: Absent[ScheduledEvent] = attrs.field(repr=True) class-attribute

The scheduled event before this event was created

GuildScheduledEventUserAdd

Bases: GuildEvent

Dispatched when scheduled event is created

Source code in interactions/api/events/discord.py
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildScheduledEventUserAdd(GuildEvent):
    """Dispatched when scheduled event is created"""

    scheduled_event_id: "Snowflake_Type" = attrs.field(repr=True)
    """The ID of the scheduled event"""
    user_id: "Snowflake_Type" = attrs.field(repr=True)
    """The ID of the user that has been added/removed from scheduled event"""

    @property
    def scheduled_event(self) -> Optional["ScheduledEvent"]:
        """The scheduled event object if cached"""
        return self.client.get_scheduled_event(self.scheduled_event_id)

    @property
    def user(self) -> Optional["User"]:
        """The user that has been added/removed from scheduled event if cached"""
        return self.client.get_user(self.user_id)

    @property
    def member(self) -> Optional["Member"]:
        """The guild member that has been added/removed from scheduled event if cached"""
        return self.client.get_member(self.guild_id, self.user.id)

member: Optional[Member] property

The guild member that has been added/removed from scheduled event if cached

scheduled_event: Optional[ScheduledEvent] property

The scheduled event object if cached

scheduled_event_id: Snowflake_Type = attrs.field(repr=True) class-attribute

The ID of the scheduled event

user: Optional[User] property

The user that has been added/removed from scheduled event if cached

user_id: Snowflake_Type = attrs.field(repr=True) class-attribute

The ID of the user that has been added/removed from scheduled event

GuildScheduledEventUserRemove

Bases: GuildScheduledEventUserAdd

Dispatched when scheduled event is removed

Source code in interactions/api/events/discord.py
905
906
907
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildScheduledEventUserRemove(GuildScheduledEventUserAdd):
    """Dispatched when scheduled event is removed"""

GuildStickersUpdate

Bases: GuildEvent

Dispatched when a guild's stickers are updated.

Source code in interactions/api/events/discord.py
360
361
362
363
364
365
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildStickersUpdate(GuildEvent):
    """Dispatched when a guild's stickers are updated."""

    stickers: List["Sticker"] = attrs.field(repr=False, factory=list)
    """List of stickers from after this event"""

stickers: List[Sticker] = attrs.field(repr=False, factory=list) class-attribute

List of stickers from after this event

GuildUnavailable

Bases: GuildEvent

Dispatched when a guild is not available.

Source code in interactions/api/events/discord.py
333
334
335
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildUnavailable(GuildEvent):
    """Dispatched when a guild is not available."""

GuildUpdate

Bases: BaseEvent

Dispatched when a guild is updated.

Source code in interactions/api/events/discord.py
303
304
305
306
307
308
309
310
311
312
313
314
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildUpdate(BaseEvent):
    """Dispatched when a guild is updated."""

    before: "Guild" = attrs.field(
        repr=False,
    )
    """Guild before this event"""
    after: "Guild" = attrs.field(
        repr=False,
    )
    """Guild after this event"""

after: Guild = attrs.field(repr=False) class-attribute

Guild after this event

before: Guild = attrs.field(repr=False) class-attribute

Guild before this event

IntegrationCreate

Bases: BaseEvent

Dispatched when a guild integration is created.

Source code in interactions/api/events/discord.py
467
468
469
470
471
472
473
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class IntegrationCreate(BaseEvent):
    """Dispatched when a guild integration is created."""

    integration: "GuildIntegration" = attrs.field(
        repr=False,
    )

IntegrationDelete

Bases: GuildEvent

Dispatched when a guild integration is deleted.

Source code in interactions/api/events/discord.py
481
482
483
484
485
486
487
488
489
490
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class IntegrationDelete(GuildEvent):
    """Dispatched when a guild integration is deleted."""

    id: "Snowflake_Type" = attrs.field(
        repr=False,
    )
    """The ID of the integration"""
    application_id: "Snowflake_Type" = attrs.field(repr=False, default=None)
    """The ID of the bot/application for this integration"""

application_id: Snowflake_Type = attrs.field(repr=False, default=None) class-attribute

The ID of the bot/application for this integration

id: Snowflake_Type = attrs.field(repr=False) class-attribute

The ID of the integration

IntegrationUpdate

Bases: IntegrationCreate

Dispatched when a guild integration is updated.

Source code in interactions/api/events/discord.py
476
477
478
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class IntegrationUpdate(IntegrationCreate):
    """Dispatched when a guild integration is updated."""

InteractionCreate

Bases: BaseEvent

Dispatched when a user uses an Application Command.

Source code in interactions/api/events/discord.py
736
737
738
739
740
741
742
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class InteractionCreate(BaseEvent):
    """Dispatched when a user uses an Application Command."""

    interaction: dict = attrs.field(
        repr=False,
    )

InviteCreate

Bases: BaseEvent

Dispatched when a guild invite is created.

Source code in interactions/api/events/discord.py
493
494
495
496
497
498
499
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class InviteCreate(BaseEvent):
    """Dispatched when a guild invite is created."""

    invite: interactions.models.Invite = attrs.field(
        repr=False,
    )

InviteDelete

Bases: InviteCreate

Dispatched when an invite is deleted.

Source code in interactions/api/events/discord.py
502
503
504
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class InviteDelete(InviteCreate):
    """Dispatched when an invite is deleted."""

MemberAdd

Bases: GuildEvent

Dispatched when a member is added to a guild.

Source code in interactions/api/events/discord.py
368
369
370
371
372
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MemberAdd(GuildEvent):
    """Dispatched when a member is added to a guild."""

    member: "Member" = attrs.field(repr=False, metadata=docs("The member who was added"))

MemberRemove

Bases: MemberAdd

Dispatched when a member is removed from a guild.

Source code in interactions/api/events/discord.py
375
376
377
378
379
380
381
382
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MemberRemove(MemberAdd):
    """Dispatched when a member is removed from a guild."""

    member: Union["Member", "User"] = attrs.field(
        repr=False,
        metadata=docs("The member who was added, can be user if the member is not cached"),
    )

MemberUpdate

Bases: GuildEvent

Dispatched when a member is updated.

Source code in interactions/api/events/discord.py
385
386
387
388
389
390
391
392
393
394
395
396
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MemberUpdate(GuildEvent):
    """Dispatched when a member is updated."""

    before: "Member" = attrs.field(
        repr=False,
    )
    """The state of the member before this event"""
    after: "Member" = attrs.field(
        repr=False,
    )
    """The state of the member after this event"""

after: Member = attrs.field(repr=False) class-attribute

The state of the member after this event

before: Member = attrs.field(repr=False) class-attribute

The state of the member before this event

MessageCreate

Bases: BaseEvent

Dispatched when a message is created.

Source code in interactions/api/events/discord.py
507
508
509
510
511
512
513
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MessageCreate(BaseEvent):
    """Dispatched when a message is created."""

    message: "Message" = attrs.field(
        repr=False,
    )

MessageDelete

Bases: BaseEvent

Dispatched when a message is deleted.

Source code in interactions/api/events/discord.py
530
531
532
533
534
535
536
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MessageDelete(BaseEvent):
    """Dispatched when a message is deleted."""

    message: "Message" = attrs.field(
        repr=False,
    )

MessageDeleteBulk

Bases: GuildEvent

Dispatched when multiple messages are deleted at once.

Source code in interactions/api/events/discord.py
539
540
541
542
543
544
545
546
547
548
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MessageDeleteBulk(GuildEvent):
    """Dispatched when multiple messages are deleted at once."""

    channel_id: "Snowflake_Type" = attrs.field(
        repr=False,
    )
    """The ID of the channel these were deleted in"""
    ids: List["Snowflake_Type"] = attrs.field(repr=False, factory=list)
    """A list of message snowflakes"""

channel_id: Snowflake_Type = attrs.field(repr=False) class-attribute

The ID of the channel these were deleted in

ids: List[Snowflake_Type] = attrs.field(repr=False, factory=list) class-attribute

A list of message snowflakes

MessagePollVoteAdd

Bases: BaseMessagePollEvent

Dispatched when a user votes in a poll

Source code in interactions/api/events/discord.py
650
651
652
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MessagePollVoteAdd(BaseMessagePollEvent):
    """Dispatched when a user votes in a poll"""

MessagePollVoteRemove

Bases: BaseMessagePollEvent

Dispatched when a user remotes a votes in a poll

Source code in interactions/api/events/discord.py
655
656
657
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MessagePollVoteRemove(BaseMessagePollEvent):
    """Dispatched when a user remotes a votes in a poll"""

MessageReactionAdd

Bases: BaseEvent

Dispatched when a reaction is added to a message.

Source code in interactions/api/events/discord.py
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MessageReactionAdd(BaseEvent):
    """Dispatched when a reaction is added to a message."""

    message: "Message" = attrs.field(repr=False, metadata=docs("The message that was reacted to"))
    emoji: "PartialEmoji" = attrs.field(repr=False, metadata=docs("The emoji that was added to the message"))
    author: Union["Member", "User"] = attrs.field(repr=False, metadata=docs("The user who added the reaction"))
    # reaction can be None when the message is not in the cache, and it was the last reaction, and it was deleted in the event
    reaction: Optional["Reaction"] = attrs.field(
        repr=False, default=None, metadata=docs("The reaction object corresponding to the emoji")
    )

    @property
    def reaction_count(self) -> int:
        """Times the emoji in the event has been used to react"""
        return 0 if self.reaction is None else self.reaction.count

reaction_count: int property

Times the emoji in the event has been used to react

MessageReactionRemove

Bases: MessageReactionAdd

Dispatched when a reaction is removed.

Source code in interactions/api/events/discord.py
569
570
571
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MessageReactionRemove(MessageReactionAdd):
    """Dispatched when a reaction is removed."""

MessageReactionRemoveAll

Bases: GuildEvent

Dispatched when all reactions are removed from a message.

Source code in interactions/api/events/discord.py
574
575
576
577
578
579
580
581
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MessageReactionRemoveAll(GuildEvent):
    """Dispatched when all reactions are removed from a message."""

    message: "Message" = attrs.field(
        repr=False,
    )
    """The message that was reacted to"""

message: Message = attrs.field(repr=False) class-attribute

The message that was reacted to

MessageReactionRemoveEmoji

Bases: MessageReactionRemoveAll

Dispatched when all reactions of a specifc emoji are removed from a message.

Source code in interactions/api/events/discord.py
584
585
586
587
588
589
590
591
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MessageReactionRemoveEmoji(MessageReactionRemoveAll):
    """Dispatched when all reactions of a specifc emoji are removed from a message."""

    emoji: "PartialEmoji" = attrs.field(
        repr=False,
    )
    """The emoji that was removed"""

emoji: PartialEmoji = attrs.field(repr=False) class-attribute

The emoji that was removed

MessageUpdate

Bases: BaseEvent

Dispatched when a message is edited.

Source code in interactions/api/events/discord.py
516
517
518
519
520
521
522
523
524
525
526
527
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class MessageUpdate(BaseEvent):
    """Dispatched when a message is edited."""

    before: "Message" = attrs.field(
        repr=False,
    )
    """The message before this event was created"""
    after: "Message" = attrs.field(
        repr=False,
    )
    """The message after this event was created"""

after: Message = attrs.field(repr=False) class-attribute

The message after this event was created

before: Message = attrs.field(repr=False) class-attribute

The message before this event was created

NewThreadCreate

Bases: ThreadCreate

Dispatched when a thread is newly created.

Source code in interactions/api/events/discord.py
220
221
222
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class NewThreadCreate(ThreadCreate):
    """Dispatched when a thread is newly created."""

PresenceUpdate

Bases: BaseEvent

A user's presence has changed.

Source code in interactions/api/events/discord.py
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class PresenceUpdate(BaseEvent):
    """A user's presence has changed."""

    user: "User" = attrs.field(
        repr=False,
    )
    """The user in question"""
    status: str = attrs.field(
        repr=False,
    )
    """'Either `idle`, `dnd`, `online`, or `offline`'"""
    activities: List["Activity"] = attrs.field(
        repr=False,
    )
    """The users current activities"""
    client_status: dict = attrs.field(
        repr=False,
    )
    """What platform the user is reported as being on"""
    guild_id: "Snowflake_Type" = attrs.field(
        repr=False,
    )
    """The guild this presence update was dispatched from"""

activities: List[Activity] = attrs.field(repr=False) class-attribute

The users current activities

client_status: dict = attrs.field(repr=False) class-attribute

What platform the user is reported as being on

guild_id: Snowflake_Type = attrs.field(repr=False) class-attribute

The guild this presence update was dispatched from

status: str = attrs.field(repr=False) class-attribute

'Either idle, dnd, online, or offline'

user: User = attrs.field(repr=False) class-attribute

The user in question

RoleCreate

Bases: GuildEvent

Dispatched when a role is created.

Source code in interactions/api/events/discord.py
399
400
401
402
403
404
405
406
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class RoleCreate(GuildEvent):
    """Dispatched when a role is created."""

    role: "Role" = attrs.field(
        repr=False,
    )
    """The created role"""

role: Role = attrs.field(repr=False) class-attribute

The created role

RoleDelete

Bases: GuildEvent

Dispatched when a guild role is deleted.

Source code in interactions/api/events/discord.py
423
424
425
426
427
428
429
430
431
432
433
434
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class RoleDelete(GuildEvent):
    """Dispatched when a guild role is deleted."""

    id: "Snowflake_Type" = attrs.field(
        repr=False,
    )
    """The ID of the deleted role"""
    role: Absent["Role"] = attrs.field(
        repr=False,
    )
    """The deleted role"""

id: Snowflake_Type = attrs.field(repr=False) class-attribute

The ID of the deleted role

role: Absent[Role] = attrs.field(repr=False) class-attribute

The deleted role

RoleUpdate

Bases: GuildEvent

Dispatched when a role is updated.

Source code in interactions/api/events/discord.py
409
410
411
412
413
414
415
416
417
418
419
420
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class RoleUpdate(GuildEvent):
    """Dispatched when a role is updated."""

    before: Absent["Role"] = attrs.field(
        repr=False,
    )
    """The role before this event"""
    after: "Role" = attrs.field(
        repr=False,
    )
    """The role after this event"""

after: Role = attrs.field(repr=False) class-attribute

The role after this event

before: Absent[Role] = attrs.field(repr=False) class-attribute

The role before this event

StageInstanceCreate

Bases: BaseEvent

Dispatched when a stage instance is created.

Source code in interactions/api/events/discord.py
686
687
688
689
690
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class StageInstanceCreate(BaseEvent):
    """Dispatched when a stage instance is created."""

    stage_instance: "StageInstance" = attrs.field(repr=False, metadata=docs("The stage instance"))

StageInstanceDelete

Bases: StageInstanceCreate

Dispatched when a stage instance is deleted.

Source code in interactions/api/events/discord.py
693
694
695
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class StageInstanceDelete(StageInstanceCreate):
    """Dispatched when a stage instance is deleted."""

StageInstanceUpdate

Bases: StageInstanceCreate

Dispatched when a stage instance is updated.

Source code in interactions/api/events/discord.py
698
699
700
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class StageInstanceUpdate(StageInstanceCreate):
    """Dispatched when a stage instance is updated."""

ThreadCreate

Bases: BaseEvent

Dispatched when a thread is created, or a thread is new to the client

Source code in interactions/api/events/discord.py
213
214
215
216
217
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ThreadCreate(BaseEvent):
    """Dispatched when a thread is created, or a thread is new to the client"""

    thread: "TYPE_THREAD_CHANNEL" = attrs.field(repr=False, metadata=docs("The thread this event is dispatched from"))

ThreadDelete

Bases: ThreadCreate

Dispatched when a thread is deleted.

Source code in interactions/api/events/discord.py
230
231
232
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ThreadDelete(ThreadCreate):
    """Dispatched when a thread is deleted."""

ThreadListSync

Bases: BaseEvent

Dispatched when gaining access to a channel, contains all active threads in that channel.

Source code in interactions/api/events/discord.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ThreadListSync(BaseEvent):
    """Dispatched when gaining access to a channel, contains all active threads in that channel."""

    channel_ids: Sequence["Snowflake_Type"] = attrs.field(
        repr=False,
    )
    """The parent channel ids whose threads are being synced. If omitted, then threads were synced for the entire guild. This array may contain channel_ids that have no active threads as well, so you know to clear that data."""
    threads: List["TYPE_ALL_CHANNEL"] = attrs.field(
        repr=False,
    )
    """all active threads in the given channels that the current user can access"""
    members: List["Member"] = attrs.field(
        repr=False,
    )
    """all thread member objects from the synced threads for the current user, indicating which threads the current user has been added to"""

channel_ids: Sequence[Snowflake_Type] = attrs.field(repr=False) class-attribute

The parent channel ids whose threads are being synced. If omitted, then threads were synced for the entire guild. This array may contain channel_ids that have no active threads as well, so you know to clear that data.

members: List[Member] = attrs.field(repr=False) class-attribute

all thread member objects from the synced threads for the current user, indicating which threads the current user has been added to

threads: List[TYPE_ALL_CHANNEL] = attrs.field(repr=False) class-attribute

all active threads in the given channels that the current user can access

ThreadMemberUpdate

Bases: ThreadCreate

Dispatched when the thread member object for the current user is updated.

??? info "Note from Discord" This event is documented for completeness, but unlikely to be used by most bots. For bots, this event largely is just a signal that you are a member of the thread

Source code in interactions/api/events/discord.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ThreadMemberUpdate(ThreadCreate):
    """
    Dispatched when the thread member object for the current user is updated.

    ??? info "Note from Discord"     This event is documented for
    completeness, but unlikely to be used by most bots. For bots, this
    event largely is just a signal that you are a member of the thread

    """

    member: "Member" = attrs.field(
        repr=False,
    )
    """The member who was added"""

member: Member = attrs.field(repr=False) class-attribute

The member who was added

ThreadMembersUpdate

Bases: GuildEvent

Dispatched when anyone is added or removed from a thread.

Source code in interactions/api/events/discord.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ThreadMembersUpdate(GuildEvent):
    """Dispatched when anyone is added or removed from a thread."""

    id: "Snowflake_Type" = attrs.field(
        repr=False,
    )
    """The ID of the thread"""
    member_count: int = attrs.field(repr=False, default=50)
    """the approximate number of members in the thread, capped at 50"""
    added_members: List["Member"] = attrs.field(repr=False, factory=list)
    """Users added to the thread"""
    removed_member_ids: List["Snowflake_Type"] = attrs.field(repr=False, factory=list)
    """Users removed from the thread"""

    @property
    def channel(self) -> Optional["TYPE_THREAD_CHANNEL"]:
        """The thread channel this event is dispatched from"""
        return self.client.get_channel(self.id)

added_members: List[Member] = attrs.field(repr=False, factory=list) class-attribute

Users added to the thread

channel: Optional[TYPE_THREAD_CHANNEL] property

The thread channel this event is dispatched from

id: Snowflake_Type = attrs.field(repr=False) class-attribute

The ID of the thread

member_count: int = attrs.field(repr=False, default=50) class-attribute

the approximate number of members in the thread, capped at 50

removed_member_ids: List[Snowflake_Type] = attrs.field(repr=False, factory=list) class-attribute

Users removed from the thread

ThreadUpdate

Bases: ThreadCreate

Dispatched when a thread is updated.

Source code in interactions/api/events/discord.py
225
226
227
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ThreadUpdate(ThreadCreate):
    """Dispatched when a thread is updated."""

TypingStart

Bases: BaseEvent

Dispatched when a user starts typing.

Source code in interactions/api/events/discord.py
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class TypingStart(BaseEvent):
    """Dispatched when a user starts typing."""

    author: Union["User", "Member"] = attrs.field(
        repr=False,
    )
    """The user who started typing"""
    channel: "TYPE_ALL_CHANNEL" = attrs.field(
        repr=False,
    )
    """The channel typing is in"""
    guild: "Guild" = attrs.field(
        repr=False,
    )
    """The ID of the guild this typing is in"""
    timestamp: "Timestamp" = attrs.field(
        repr=False,
    )
    """unix time (in seconds) of when the user started typing"""

author: Union[User, Member] = attrs.field(repr=False) class-attribute

The user who started typing

channel: TYPE_ALL_CHANNEL = attrs.field(repr=False) class-attribute

The channel typing is in

guild: Guild = attrs.field(repr=False) class-attribute

The ID of the guild this typing is in

timestamp: Timestamp = attrs.field(repr=False) class-attribute

unix time (in seconds) of when the user started typing

VoiceStateUpdate

Bases: BaseEvent

Dispatched when a user's voice state changes.

Source code in interactions/api/events/discord.py
745
746
747
748
749
750
751
752
753
754
755
756
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class VoiceStateUpdate(BaseEvent):
    """Dispatched when a user's voice state changes."""

    before: Optional["VoiceState"] = attrs.field(
        repr=False,
    )
    """The voice state before this event was created or None if the user was not in a voice channel"""
    after: Optional["VoiceState"] = attrs.field(
        repr=False,
    )
    """The voice state after this event was created or None if the user is no longer in a voice channel"""

after: Optional[VoiceState] = attrs.field(repr=False) class-attribute

The voice state after this event was created or None if the user is no longer in a voice channel

before: Optional[VoiceState] = attrs.field(repr=False) class-attribute

The voice state before this event was created or None if the user was not in a voice channel

VoiceUserDeafen

Bases: BaseVoiceEvent

Dispatched when a user is deafened or undeafened.

Source code in interactions/api/events/discord.py
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class VoiceUserDeafen(BaseVoiceEvent):
    """Dispatched when a user is deafened or undeafened."""

    author: Union["User", "Member"] = attrs.field(
        repr=False,
    )
    """The user who was deafened or undeafened"""
    channel: "VoiceChannel" = attrs.field(
        repr=False,
    )
    """The voice channel the user was deafened or undeafened in"""
    deaf: bool = attrs.field(
        repr=False,
    )
    """The new deaf state of the user"""

author: Union[User, Member] = attrs.field(repr=False) class-attribute

The user who was deafened or undeafened

channel: VoiceChannel = attrs.field(repr=False) class-attribute

The voice channel the user was deafened or undeafened in

deaf: bool = attrs.field(repr=False) class-attribute

The new deaf state of the user

VoiceUserJoin

Bases: BaseVoiceEvent

Dispatched when a user joins a voice channel.

Source code in interactions/api/events/discord.py
821
822
823
824
825
826
827
828
829
830
831
832
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class VoiceUserJoin(BaseVoiceEvent):
    """Dispatched when a user joins a voice channel."""

    author: Union["User", "Member"] = attrs.field(
        repr=False,
    )
    """The user who joined the voice channel"""
    channel: "VoiceChannel" = attrs.field(
        repr=False,
    )
    """The voice channel the user joined"""

author: Union[User, Member] = attrs.field(repr=False) class-attribute

The user who joined the voice channel

channel: VoiceChannel = attrs.field(repr=False) class-attribute

The voice channel the user joined

VoiceUserLeave

Bases: BaseVoiceEvent

Dispatched when a user leaves a voice channel.

Source code in interactions/api/events/discord.py
835
836
837
838
839
840
841
842
843
844
845
846
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class VoiceUserLeave(BaseVoiceEvent):
    """Dispatched when a user leaves a voice channel."""

    author: Union["User", "Member"] = attrs.field(
        repr=False,
    )
    """The user who left the voice channel"""
    channel: "VoiceChannel" = attrs.field(
        repr=False,
    )
    """The voice channel the user left"""

author: Union[User, Member] = attrs.field(repr=False) class-attribute

The user who left the voice channel

channel: VoiceChannel = attrs.field(repr=False) class-attribute

The voice channel the user left

VoiceUserMove

Bases: BaseVoiceEvent

Dispatched when a user moves voice channels.

Source code in interactions/api/events/discord.py
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class VoiceUserMove(BaseVoiceEvent):
    """Dispatched when a user moves voice channels."""

    author: Union["User", "Member"] = attrs.field(
        repr=False,
    )

    previous_channel: "VoiceChannel" = attrs.field(
        repr=False,
    )
    """The previous voice channel the user was in"""
    new_channel: "VoiceChannel" = attrs.field(
        repr=False,
    )
    """The new voice channel the user is in"""

new_channel: VoiceChannel = attrs.field(repr=False) class-attribute

The new voice channel the user is in

previous_channel: VoiceChannel = attrs.field(repr=False) class-attribute

The previous voice channel the user was in

VoiceUserMute

Bases: BaseVoiceEvent

Dispatched when a user is muted or unmuted.

Source code in interactions/api/events/discord.py
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class VoiceUserMute(BaseVoiceEvent):
    """Dispatched when a user is muted or unmuted."""

    author: Union["User", "Member"] = attrs.field(
        repr=False,
    )
    """The user who was muted or unmuted"""
    channel: "VoiceChannel" = attrs.field(
        repr=False,
    )
    """The voice channel the user was muted or unmuted in"""
    mute: bool = attrs.field(
        repr=False,
    )
    """The new mute state of the user"""

author: Union[User, Member] = attrs.field(repr=False) class-attribute

The user who was muted or unmuted

channel: VoiceChannel = attrs.field(repr=False) class-attribute

The voice channel the user was muted or unmuted in

mute: bool = attrs.field(repr=False) class-attribute

The new mute state of the user

WebhooksUpdate

Bases: GuildEvent

Dispatched when a guild channel webhook is created, updated, or deleted.

Source code in interactions/api/events/discord.py
725
726
727
728
729
730
731
732
733
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class WebhooksUpdate(GuildEvent):
    """Dispatched when a guild channel webhook is created, updated, or deleted."""

    # Discord doesnt sent the webhook object for this event, for some reason
    channel_id: "Snowflake_Type" = attrs.field(
        repr=False,
    )
    """The ID of the webhook was updated"""

channel_id: Snowflake_Type = attrs.field(repr=False) class-attribute

The ID of the webhook was updated

These are events dispatched by the client. This is intended as a reference so you know what data to expect for each event.

Example Usage

To listen to an event, use the listen decorator:

1
2
3
4
5
6
from interactions import listen
from interactions.api.events import ChannelCreate  # or any other event

@listen(ChannelCreate)
async def an_event_handler(event: ChannelCreate):
    print(f"Channel created with name: {event.channel.name}")

For more information, including other ways to listen to events, see the events guide.

Warning

While all of these events are documented, not all of them are used, currently.

AutocompleteCompletion

Bases: BaseEvent

Dispatched after the library ran any autocomplete callback.

Source code in interactions/api/events/internal.py
170
171
172
173
174
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class AutocompleteCompletion(BaseEvent):
    """Dispatched after the library ran any autocomplete callback."""

    ctx: "AutocompleteContext" = attrs.field(repr=False, metadata=docs("The autocomplete context"))

AutocompleteError

Bases: _Error

Dispatched when the library encounters an error in an autocomplete.

Source code in interactions/api/events/internal.py
215
216
217
218
219
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class AutocompleteError(_Error):
    """Dispatched when the library encounters an error in an autocomplete."""

    ctx: "AutocompleteContext" = attrs.field(repr=False, metadata=docs("The autocomplete context"))

ButtonPressed

Bases: Component

Dispatched when a user uses a Button.

Source code in interactions/api/events/internal.py
146
147
148
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ButtonPressed(Component):
    """Dispatched when a user uses a Button."""

CallbackAdded

Bases: BaseEvent

Dispatched when a callback is added to the client.

Source code in interactions/api/events/internal.py
257
258
259
260
261
262
263
264
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class CallbackAdded(BaseEvent):
    """Dispatched when a callback is added to the client."""

    callback: "BaseCommand | Listener" = attrs.field(repr=False)
    """The callback that was added"""
    extension: "Extension | None" = attrs.field(repr=False, default=None)
    """The extension that the command was added from, if any"""

callback: BaseCommand | Listener = attrs.field(repr=False) class-attribute

The callback that was added

extension: Extension | None = attrs.field(repr=False, default=None) class-attribute

The extension that the command was added from, if any

CommandCompletion

Bases: BaseEvent

Dispatched after the library ran any command callback.

Source code in interactions/api/events/internal.py
156
157
158
159
160
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class CommandCompletion(BaseEvent):
    """Dispatched after the library ran any command callback."""

    ctx: "BaseContext" = attrs.field(repr=False, metadata=docs("The command context"))

CommandError

Bases: _Error

Dispatched when the library encounters an error in a command.

Source code in interactions/api/events/internal.py
201
202
203
204
205
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class CommandError(_Error):
    """Dispatched when the library encounters an error in a command."""

    ctx: "BaseContext" = attrs.field(repr=False, metadata=docs("The command context"))

Component

Bases: BaseEvent

Dispatched when a user uses a Component.

Source code in interactions/api/events/internal.py
139
140
141
142
143
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class Component(BaseEvent):
    """Dispatched when a user uses a Component."""

    ctx: "ComponentContext" = attrs.field(repr=False, metadata=docs("The context of the interaction"))

ComponentCompletion

Bases: BaseEvent

Dispatched after the library ran any component callback.

Source code in interactions/api/events/internal.py
163
164
165
166
167
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ComponentCompletion(BaseEvent):
    """Dispatched after the library ran any component callback."""

    ctx: "ComponentContext" = attrs.field(repr=False, metadata=docs("The component context"))

ComponentError

Bases: _Error

Dispatched when the library encounters an error in a component.

Source code in interactions/api/events/internal.py
208
209
210
211
212
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ComponentError(_Error):
    """Dispatched when the library encounters an error in a component."""

    ctx: "ComponentContext" = attrs.field(repr=False, metadata=docs("The component context"))

Connect

Bases: BaseEvent

The bot is now connected to the discord Gateway.

Source code in interactions/api/events/internal.py
80
81
82
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class Connect(BaseEvent):
    """The bot is now connected to the discord Gateway."""

Disconnect

Bases: BaseEvent

The bot has just disconnected.

Source code in interactions/api/events/internal.py
90
91
92
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class Disconnect(BaseEvent):
    """The bot has just disconnected."""

Error

Bases: _Error

Dispatched when the library encounters an error.

Source code in interactions/api/events/internal.py
191
192
193
194
195
196
197
198
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class Error(_Error):
    """Dispatched when the library encounters an error."""

    source: str = attrs.field(repr=False, metadata=docs("The source of the error"))
    ctx: Optional["BaseContext"] = attrs.field(
        repr=False, default=None, metadata=docs("The Context, if one was active")
    )

ExtensionCommandParse

Bases: ExtensionLoad

Dispatched when an extension is parsed for commands.

Source code in interactions/api/events/internal.py
249
250
251
252
253
254
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ExtensionCommandParse(ExtensionLoad):
    """Dispatched when an extension is parsed for commands."""

    callables: list[tuple[str, typing.Callable]] = attrs.field(repr=False, default=None)
    """The callables that were parsed for commands"""

callables: list[tuple[str, typing.Callable]] = attrs.field(repr=False, default=None) class-attribute

The callables that were parsed for commands

ExtensionLoad

Bases: BaseEvent

Dispatched when an extension is loaded.

Source code in interactions/api/events/internal.py
229
230
231
232
233
234
235
236
237
238
239
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ExtensionLoad(BaseEvent):
    """Dispatched when an extension is loaded."""

    extension: "Extension" = attrs.field(repr=False)
    """The extension in question"""

    @property
    def metadata(self) -> "Type[Extension.Metadata] | None":
        """The metadata of the extension, if it has any."""
        return self.extension.Metadata or None

extension: Extension = attrs.field(repr=False) class-attribute

The extension in question

metadata: Type[Extension.Metadata] | None property

The metadata of the extension, if it has any.

ExtensionUnload

Bases: ExtensionLoad

Dispatched when an extension is unloaded.

Source code in interactions/api/events/internal.py
242
243
244
245
246
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ExtensionUnload(ExtensionLoad):
    """Dispatched when an extension is unloaded."""

    ...

Login

Bases: BaseEvent

The bot has just logged in.

Source code in interactions/api/events/internal.py
75
76
77
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class Login(BaseEvent):
    """The bot has just logged in."""

ModalCompletion

Bases: BaseEvent

Dispatched after the library ran any modal callback.

Source code in interactions/api/events/internal.py
177
178
179
180
181
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ModalCompletion(BaseEvent):
    """Dispatched after the library ran any modal callback."""

    ctx: "ModalContext" = attrs.field(repr=False, metadata=docs("The modal context"))

ModalError

Bases: _Error

Dispatched when the library encounters an error in a modal.

Source code in interactions/api/events/internal.py
222
223
224
225
226
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
class ModalError(_Error):
    """Dispatched when the library encounters an error in a modal."""

    ctx: "ModalContext" = attrs.field(repr=False, metadata=docs("The modal context"))

Ready

Bases: BaseEvent

The client is now ready.

Note

Don't use this event for things that must only happen once, on startup, as this event may be called multiple times. Instead, use the Startup event

Source code in interactions/api/events/internal.py
120
121
122
123
124
125
126
127
128
129
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class Ready(BaseEvent):
    """
    The client is now ready.

    !!! note
        Don't use this event for things that must only happen once, on startup, as this event may be called multiple times.
        Instead, use the `Startup` event

    """

Resume

Bases: BaseEvent

The bot has resumed its connection to the discord Gateway.

Source code in interactions/api/events/internal.py
85
86
87
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class Resume(BaseEvent):
    """The bot has resumed its connection to the discord Gateway."""

Select

Bases: Component

Dispatched when a user uses a Select.

Source code in interactions/api/events/internal.py
151
152
153
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class Select(Component):
    """Dispatched when a user uses a Select."""

ShardConnect

Bases: Connect

A shard just connected to the discord Gateway.

Source code in interactions/api/events/internal.py
95
96
97
98
99
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ShardConnect(Connect):
    """A shard just connected to the discord Gateway."""

    shard_id: int = attrs.field(repr=False, metadata=docs("The ID of the shard"))

ShardDisconnect

Bases: Disconnect

A shard just disconnected.

Source code in interactions/api/events/internal.py
102
103
104
105
106
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class ShardDisconnect(Disconnect):
    """A shard just disconnected."""

    shard_id: int = attrs.field(repr=False, metadata=docs("The ID of the shard"))

Startup

Bases: BaseEvent

The client is now ready for the first time.

Use this for tasks you want to do upon login, instead of ready, as this will only be called once.

Source code in interactions/api/events/internal.py
109
110
111
112
113
114
115
116
117
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class Startup(BaseEvent):
    """
    The client is now ready for the first time.

    Use this for tasks you want to do upon login, instead of ready, as
    this will only be called once.

    """

WebsocketReady

Bases: RawGatewayEvent

The gateway has reported that it is ready.

Source code in interactions/api/events/internal.py
132
133
134
135
136
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class WebsocketReady(RawGatewayEvent):
    """The gateway has reported that it is ready."""

    data: dict = attrs.field(repr=False, metadata=docs("The data from the ready event"))