Compare commits
13 Commits
fix_hangma
...
main
Author | SHA1 | Date | |
---|---|---|---|
f21cbba726 | |||
28387206f7 | |||
11fbca9b30 | |||
8775e09589 | |||
098231bdeb | |||
fcdfb3e35d | |||
cad899b767 | |||
ba03d4063f | |||
72e0514ed0 | |||
971e66e1ec | |||
89d0c0b9fb | |||
816a3b7de8 | |||
b449e453a4 |
@ -1,6 +1,4 @@
|
|||||||
# Gwendolyn
|
# Gwendolyn
|
||||||
[](https://www.codacy.com?utm_source=github.com&utm_medium=referral&utm_content=NikolajDanger/Gwendolyn&utm_campaign=Badge_Grade)
|
|
||||||
|
|
||||||
Gwendolyn is a discord bot that I made. It does a bunch of stuff.
|
Gwendolyn is a discord bot that I made. It does a bunch of stuff.
|
||||||
|
|
||||||
## Stuff it can do
|
## Stuff it can do
|
||||||
|
@ -98,6 +98,23 @@ class HangmanCog(commands.Cog):
|
|||||||
"""Start a game of hangman."""
|
"""Start a game of hangman."""
|
||||||
await self.bot.games.hangman.start(ctx)
|
await self.bot.games.hangman.start(ctx)
|
||||||
|
|
||||||
|
class WordleCog(commands.Cog):
|
||||||
|
"""Contains all the wordle commands."""
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
"""Initialize the cog."""
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@cog_ext.cog_subcommand(**params["wordle_start"])
|
||||||
|
async def wordle_start(self, ctx, letters = 5):
|
||||||
|
"""Start a game of wordle."""
|
||||||
|
await self.bot.games.wordle.start(ctx, letters)
|
||||||
|
|
||||||
|
@cog_ext.cog_subcommand(**params["wordle_guess"])
|
||||||
|
async def wordle_guess(self, ctx, guess):
|
||||||
|
"""Start a game of wordle."""
|
||||||
|
await self.bot.games.wordle.guess(ctx, guess)
|
||||||
|
|
||||||
|
|
||||||
class HexCog(commands.Cog):
|
class HexCog(commands.Cog):
|
||||||
"""Contains all the hex commands."""
|
"""Contains all the hex commands."""
|
||||||
@ -145,3 +162,4 @@ def setup(bot):
|
|||||||
bot.add_cog(ConnectFourCog(bot))
|
bot.add_cog(ConnectFourCog(bot))
|
||||||
bot.add_cog(HangmanCog(bot))
|
bot.add_cog(HangmanCog(bot))
|
||||||
bot.add_cog(HexCog(bot))
|
bot.add_cog(HexCog(bot))
|
||||||
|
bot.add_cog(WordleCog(bot))
|
||||||
|
@ -92,7 +92,7 @@ class MiscCog(commands.Cog):
|
|||||||
@cog_ext.cog_slash(**params["wolf"])
|
@cog_ext.cog_slash(**params["wolf"])
|
||||||
async def wolf(self, ctx: SlashContext, query):
|
async def wolf(self, ctx: SlashContext, query):
|
||||||
"""Perform a search on Wolfram Alpha."""
|
"""Perform a search on Wolfram Alpha."""
|
||||||
await self.nerd_shit.wolfSearch(ctx, query)
|
await self.nerd_shit.wolf_search(ctx, query)
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
|
@ -758,7 +758,7 @@ class Blackjack(CardGame):
|
|||||||
game_id = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
|
game_id = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
|
||||||
|
|
||||||
new_game = {
|
new_game = {
|
||||||
"_id": channel, "dealer hand": dealer_hand,
|
"dealer hand": dealer_hand,
|
||||||
"dealer busted": False, "game_id": game_id,
|
"dealer busted": False, "game_id": game_id,
|
||||||
"dealer blackjack": (self._calc_hand_value(dealer_hand) == 21),
|
"dealer blackjack": (self._calc_hand_value(dealer_hand) == 21),
|
||||||
"user hands": {}, "all standing": False, "round": 0
|
"user hands": {}, "all standing": False, "round": 0
|
||||||
|
@ -108,7 +108,7 @@ class ConnectFour(BoardGame):
|
|||||||
await self.bot.defer(ctx)
|
await self.bot.defer(ctx)
|
||||||
channel = str(ctx.channel_id)
|
channel = str(ctx.channel_id)
|
||||||
|
|
||||||
opponent_info = await self._test_opponent(ctx, opponent)
|
opponent, opponent_info = await self._test_opponent(ctx, opponent)
|
||||||
if not opponent_info:
|
if not opponent_info:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ class ConnectFour(BoardGame):
|
|||||||
players = [ctx.author.id, opponent]
|
players = [ctx.author.id, opponent]
|
||||||
random.shuffle(players)
|
random.shuffle(players)
|
||||||
|
|
||||||
self.draw.draw_image(channel, board, players, [0, [0,0], ""], players)
|
self.draw.draw_image(channel, board, players, [0, [0,0], ""])
|
||||||
|
|
||||||
opponent_name = self.get_name(f"#{opponent}")
|
opponent_name = self.get_name(f"#{opponent}")
|
||||||
turn_name = self.get_name(f"#{players[0]}")
|
turn_name = self.get_name(f"#{players[0]}")
|
||||||
@ -252,7 +252,11 @@ class ConnectFour(BoardGame):
|
|||||||
if placed_piece:
|
if placed_piece:
|
||||||
channel = str(ctx.channel)
|
channel = str(ctx.channel)
|
||||||
self.draw.draw_image(
|
self.draw.draw_image(
|
||||||
channel, board, winner, win_coordinates, win_direction, players)
|
channel,
|
||||||
|
board,
|
||||||
|
players,
|
||||||
|
[winner, win_coordinates, win_direction]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
boards_path = "gwendolyn/resources/games/connect_four_boards/"
|
boards_path = "gwendolyn/resources/games/connect_four_boards/"
|
||||||
|
@ -105,9 +105,11 @@ class DatabaseGame(GameBase):
|
|||||||
file_pointer.write(str(old_image.id))
|
file_pointer.write(str(old_image.id))
|
||||||
|
|
||||||
async def _start_new(self, channel: Messageable, new_game: dict,
|
async def _start_new(self, channel: Messageable, new_game: dict,
|
||||||
buttons: list[tuple[str, list]] = None):
|
buttons: list[tuple[str, list]] = None,
|
||||||
|
delete = True):
|
||||||
|
new_game['_id'] = str(channel.id)
|
||||||
self._insert_document(new_game)
|
self._insert_document(new_game)
|
||||||
await self._send_image(channel, buttons)
|
await self._send_image(channel, buttons, delete)
|
||||||
|
|
||||||
async def _end_game(self, channel: Messageable):
|
async def _end_game(self, channel: Messageable):
|
||||||
await self._delete_old_image(channel)
|
await self._delete_old_image(channel)
|
||||||
@ -187,7 +189,7 @@ class BoardGame(GameBase):
|
|||||||
self.bot.log("They tried to play against themself")
|
self.bot.log("They tried to play against themself")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return difficulty, difficulty_text
|
return opponent, (difficulty, difficulty_text)
|
||||||
|
|
||||||
class BaseDrawer():
|
class BaseDrawer():
|
||||||
"""Class for drawing games."""
|
"""Class for drawing games."""
|
||||||
@ -200,14 +202,22 @@ class BaseDrawer():
|
|||||||
self.resources = game.resources
|
self.resources = game.resources
|
||||||
game_name = game.game_name
|
game_name = game.game_name
|
||||||
self.default_image = f"{self.resources}default_images/{game_name}.png"
|
self.default_image = f"{self.resources}default_images/{game_name}.png"
|
||||||
|
self.default_size = None
|
||||||
|
self.default_color = None
|
||||||
self.images_path = f"{self.resources}images/{game_name}"
|
self.images_path = f"{self.resources}images/{game_name}"
|
||||||
|
|
||||||
def _draw_image(self, game: dict, table: Image.Image):
|
def _get_size(self, game: dict):
|
||||||
|
return self.default_size
|
||||||
|
|
||||||
|
def _draw_image(self, game: dict, image: Image.Image):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def draw(self, channel: str):
|
def draw(self, channel: str):
|
||||||
game = self.game.access_document(channel)
|
game = self.game.access_document(channel)
|
||||||
image = Image.open(self.default_image)
|
if self.default_image is not None:
|
||||||
|
image = Image.open(self.default_image)
|
||||||
|
else:
|
||||||
|
image = Image.new("RGB", self._get_size(game), self.default_color)
|
||||||
self._draw_image(game, image)
|
self._draw_image(game, image)
|
||||||
self._save_image(image, channel)
|
self._save_image(image, channel)
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from .blackjack import Blackjack
|
|||||||
from .connect_four import ConnectFour
|
from .connect_four import ConnectFour
|
||||||
from .hangman import Hangman
|
from .hangman import Hangman
|
||||||
from .hex import HexGame
|
from .hex import HexGame
|
||||||
|
from .wordle import WordleGame
|
||||||
|
|
||||||
|
|
||||||
class Games():
|
class Games():
|
||||||
@ -46,3 +47,4 @@ class Games():
|
|||||||
self.connect_four = ConnectFour(bot)
|
self.connect_four = ConnectFour(bot)
|
||||||
self.hangman = Hangman(bot)
|
self.hangman = Hangman(bot)
|
||||||
self.hex = HexGame(bot)
|
self.hex = HexGame(bot)
|
||||||
|
self.wordle = WordleGame(bot)
|
||||||
|
201
gwendolyn/funcs/games/wordle.py
Normal file
201
gwendolyn/funcs/games/wordle.py
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
"""Implementation of Wordle"""
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
|
from .game_base import DatabaseGame, BaseDrawer
|
||||||
|
|
||||||
|
from gwendolyn.exceptions import GameNotInDatabase
|
||||||
|
|
||||||
|
IMAGE_MARGIN = 90
|
||||||
|
SQUARE_SIZE = 150
|
||||||
|
SQUARE_PADDING = 25
|
||||||
|
ROW_PADDING = 30
|
||||||
|
FONT_SIZE = 130
|
||||||
|
|
||||||
|
# COLORS
|
||||||
|
BACKGROUND_COLOR = "#2c2f33"
|
||||||
|
TEXT_COLOR = "#eee"
|
||||||
|
WRITING_BORDER_COLOR = "#999"
|
||||||
|
INACTIVE_BORDER_COLOR = "#555"
|
||||||
|
INCORRECT_COLOR = "#444"
|
||||||
|
CORRECT_COLOR = "#0a8c20"
|
||||||
|
PRESENT_COLOR = "#0f7aa8"
|
||||||
|
|
||||||
|
COLORS = [INCORRECT_COLOR, PRESENT_COLOR, CORRECT_COLOR]
|
||||||
|
|
||||||
|
class WordleGame(DatabaseGame):
|
||||||
|
"""Implementation of wordle game."""
|
||||||
|
def __init__(self, bot):
|
||||||
|
super().__init__(bot, "wordle", WordleDrawer)
|
||||||
|
self._API_url = "https://api.wordnik.com/v4/words.json/randomWords?" # pylint: disable=invalid-name
|
||||||
|
api_key = self.bot.credentials["wordnik_key"]
|
||||||
|
self._APIPARAMS = { # pylint: disable=invalid-name
|
||||||
|
"hasDictionaryDef": True,
|
||||||
|
"minCorpusCount": 5000,
|
||||||
|
"maxCorpusCount": -1,
|
||||||
|
"minDictionaryCount": 1,
|
||||||
|
"maxDictionaryCount": -1,
|
||||||
|
"limit": 1,
|
||||||
|
"api_key": api_key
|
||||||
|
}
|
||||||
|
|
||||||
|
async def start(self, ctx, letters: int):
|
||||||
|
if self._test_document(str(ctx.channel_id)):
|
||||||
|
await ctx.send("There is already a wordle game in this channel.")
|
||||||
|
self.bot.log("There was already a game going on")
|
||||||
|
return
|
||||||
|
|
||||||
|
params = self._APIPARAMS
|
||||||
|
params["minLength"] = letters
|
||||||
|
params["maxLength"] = letters
|
||||||
|
word = "-"
|
||||||
|
while "-" in word or "." in word:
|
||||||
|
response = requests.get(self._API_url, params=params)
|
||||||
|
if response.json() == []:
|
||||||
|
ctx.send("Could not find a word. Try again")
|
||||||
|
return
|
||||||
|
word = list(response.json()[0]["word"].upper())
|
||||||
|
|
||||||
|
self.bot.log(f"Found the word \"{''.join(word)}\"")
|
||||||
|
game = {"word": word, "guesses": [], "results": []}
|
||||||
|
await ctx.send("Starting a wordle game.")
|
||||||
|
await self._start_new(ctx.channel, game, delete=False)
|
||||||
|
|
||||||
|
def _get_result(self, guess: list[str], word: list[str]):
|
||||||
|
result = ["_" for _ in guess]
|
||||||
|
for i, letter in enumerate(guess):
|
||||||
|
if letter == word[i]:
|
||||||
|
result[i] = "*"
|
||||||
|
|
||||||
|
for i, letter in enumerate(guess):
|
||||||
|
if letter in word and result[i] != "*":
|
||||||
|
same_letter = guess[:i].count(letter)
|
||||||
|
same_letter += len(
|
||||||
|
[
|
||||||
|
l for j,l in
|
||||||
|
enumerate(guess[i:])
|
||||||
|
if guess[i:][j] == letter and result[i:][j] == "*"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if same_letter < word.count(letter):
|
||||||
|
result[i] = "-"
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def guess(self, ctx, guess: str):
|
||||||
|
if not guess.isalpha():
|
||||||
|
await ctx.send("You can only use letters in your guess")
|
||||||
|
return
|
||||||
|
|
||||||
|
guess = list(guess.upper())
|
||||||
|
try:
|
||||||
|
game = self.access_document(str(ctx.channel_id))
|
||||||
|
except GameNotInDatabase:
|
||||||
|
await ctx.send("No game in channel")
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(guess) != len(game['word']):
|
||||||
|
await ctx.send(
|
||||||
|
f"Your guess must be {len(game['word'])} letters long")
|
||||||
|
return
|
||||||
|
|
||||||
|
await ctx.send(f"Guessed {''.join(guess)}")
|
||||||
|
result = self._get_result(guess, game['word'])
|
||||||
|
|
||||||
|
updater = {
|
||||||
|
"$set": {
|
||||||
|
f"guesses.{len(game['guesses'])}": guess,
|
||||||
|
f"results.{len(game['guesses'])}": result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self._update_document(str(ctx.channel_id), updater)
|
||||||
|
|
||||||
|
if result == ["*" for _ in game['word']]:
|
||||||
|
await ctx.send("You guessed the word! Adding 15 GwendoBucks to your account")
|
||||||
|
self.bot.money.addMoney(f"#{ctx.author_id}", 15)
|
||||||
|
await self._end_game(ctx.channel)
|
||||||
|
elif len(game['guesses']) == 5:
|
||||||
|
await ctx.send(f"You used up all available guesses. The word was '{''.join(game['word'])}'")
|
||||||
|
await self._end_game(ctx.channel)
|
||||||
|
else:
|
||||||
|
print(len(game['guesses']))
|
||||||
|
await self._delete_old_image(ctx.channel)
|
||||||
|
await self._send_image(ctx.channel, delete=False)
|
||||||
|
|
||||||
|
class WordleDrawer(BaseDrawer):
|
||||||
|
def __init__(self, bot, game: WordleGame):
|
||||||
|
super().__init__(bot, game)
|
||||||
|
self.default_image = None
|
||||||
|
self.default_color = BACKGROUND_COLOR
|
||||||
|
self.default_size = (0, 0)
|
||||||
|
|
||||||
|
def _get_size(self, game: dict):
|
||||||
|
width = (
|
||||||
|
(len(game['word']) * SQUARE_SIZE) +
|
||||||
|
((len(game['word']) - 1) * SQUARE_PADDING) +
|
||||||
|
(2 * IMAGE_MARGIN)
|
||||||
|
)
|
||||||
|
height = (
|
||||||
|
(6 * SQUARE_SIZE) +
|
||||||
|
(5 * ROW_PADDING) +
|
||||||
|
(2 * IMAGE_MARGIN)
|
||||||
|
)
|
||||||
|
size = (width, height)
|
||||||
|
|
||||||
|
return size
|
||||||
|
|
||||||
|
def _draw_row(self, row, drawer, font, word: str, colors: list[str] = None,
|
||||||
|
border_color: str = None):
|
||||||
|
if colors is None:
|
||||||
|
colors = [BACKGROUND_COLOR for _ in range(len(word))]
|
||||||
|
|
||||||
|
for i, letter in enumerate(word):
|
||||||
|
y_pos = IMAGE_MARGIN + (row * (SQUARE_SIZE + ROW_PADDING))
|
||||||
|
x_pos = IMAGE_MARGIN + (i * (SQUARE_SIZE + SQUARE_PADDING))
|
||||||
|
top_left = (x_pos, y_pos)
|
||||||
|
bottom_right = (x_pos + SQUARE_SIZE, y_pos + SQUARE_SIZE)
|
||||||
|
|
||||||
|
drawer.rounded_rectangle(
|
||||||
|
(top_left,bottom_right),
|
||||||
|
10,
|
||||||
|
colors[i],
|
||||||
|
border_color,
|
||||||
|
3
|
||||||
|
)
|
||||||
|
|
||||||
|
text_pos = (
|
||||||
|
x_pos + (SQUARE_SIZE//2),
|
||||||
|
y_pos + int(SQUARE_SIZE * 0.6)
|
||||||
|
)
|
||||||
|
|
||||||
|
drawer.text(text_pos, letter, TEXT_COLOR, font=font, anchor="mm")
|
||||||
|
|
||||||
|
def _determine_colors(self, results):
|
||||||
|
return [COLORS[["_","-","*"].index(symbol)] for symbol in results]
|
||||||
|
|
||||||
|
def _draw_image(self, game: dict, image: Image.Image):
|
||||||
|
drawer = ImageDraw.Draw(image)
|
||||||
|
font = self._get_font(FONT_SIZE)
|
||||||
|
for i, guess in enumerate(game['guesses']):
|
||||||
|
colors = self._determine_colors(game['results'][i])
|
||||||
|
self._draw_row(i, drawer, font, guess, colors)
|
||||||
|
|
||||||
|
if len(game["guesses"]) < 6:
|
||||||
|
self._draw_row(
|
||||||
|
len(game['guesses']),
|
||||||
|
drawer,
|
||||||
|
font,
|
||||||
|
" "*len(game['word']),
|
||||||
|
border_color=WRITING_BORDER_COLOR
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in range(5 - len(game['guesses'])):
|
||||||
|
self._draw_row(
|
||||||
|
len(game['guesses']) + i + 1,
|
||||||
|
drawer,
|
||||||
|
font, " "*len(game['word']),
|
||||||
|
border_color=INACTIVE_BORDER_COLOR
|
||||||
|
)
|
||||||
|
|
||||||
|
return super()._draw_image(game, image)
|
@ -1,13 +1,13 @@
|
|||||||
import random # Used in movieFunc
|
import random # Used in movie_func
|
||||||
import datetime # Used in helloFunc
|
import datetime # Used in hello_func
|
||||||
import urllib # Used in imageFunc
|
import urllib # Used in image_func
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
import imdb # Used in movieFunc
|
import imdb # Used in movie_func
|
||||||
import discord # Used in movieFunc
|
import discord # Used in movie_func
|
||||||
import lxml # Used in imageFunc
|
import lxml # Used in image_func
|
||||||
import fandom # Used in findWikiPage
|
import fandom # Used in find_wiki_page
|
||||||
import d20 # Used in rollDice
|
import d20 # Used in roll_dice
|
||||||
|
|
||||||
from .plex import Plex
|
from .plex import Plex
|
||||||
from .nerd_shit import NerdShit
|
from .nerd_shit import NerdShit
|
||||||
@ -93,7 +93,10 @@ class Other():
|
|||||||
cam = random.choice(cams)
|
cam = random.choice(cams)
|
||||||
self.bot.log("Chose cam type "+cam)
|
self.bot.log("Chose cam type "+cam)
|
||||||
if cam == "one":
|
if cam == "one":
|
||||||
search = f"img_{''.join([random.randint(0,9) for _ in range(4)])}"
|
|
||||||
|
search = "img_" + ''.join(
|
||||||
|
[str(random.randint(0,9)) for _ in range(4)]
|
||||||
|
)
|
||||||
elif cam == "two":
|
elif cam == "two":
|
||||||
year = str(random.randint(2012,2016))
|
year = str(random.randint(2012,2016))
|
||||||
month = str(random.randint(1,12)).zfill(2)
|
month = str(random.randint(1,12)).zfill(2)
|
||||||
@ -102,7 +105,9 @@ class Other():
|
|||||||
elif cam == "three":
|
elif cam == "three":
|
||||||
search = f"IMAG_{str(random.randint(1,500)).zfill(4)}"
|
search = f"IMAG_{str(random.randint(1,500)).zfill(4)}"
|
||||||
elif cam == "four":
|
elif cam == "four":
|
||||||
search = f"DSC_{''.join([random.randint(0,9) for _ in range(4)])}"
|
search = "DSC_" + ''.join(
|
||||||
|
[str(random.randint(0,9)) for _ in range(4)]
|
||||||
|
)
|
||||||
|
|
||||||
self.bot.log("Searching for "+search)
|
self.bot.log("Searching for "+search)
|
||||||
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 586 KiB After Width: | Height: | Size: 638 KiB |
@ -383,5 +383,31 @@
|
|||||||
"required" : "true"
|
"required" : "true"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"wordle_start": {
|
||||||
|
"base": "wordle",
|
||||||
|
"name" : "start",
|
||||||
|
"description": "Start a game of wordle",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "letters",
|
||||||
|
"description" : "How many letters the word should be",
|
||||||
|
"type": 4,
|
||||||
|
"required": "false"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"wordle_guess": {
|
||||||
|
"base": "wordle",
|
||||||
|
"name" : "guess",
|
||||||
|
"description": "Guess a word in wordle",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "guess",
|
||||||
|
"description" : "Your guess",
|
||||||
|
"type": 3,
|
||||||
|
"required": "true"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -107,7 +107,8 @@ class DatabaseFuncs():
|
|||||||
"trivia questions",
|
"trivia questions",
|
||||||
"blackjack games",
|
"blackjack games",
|
||||||
"connect 4 games"
|
"connect 4 games"
|
||||||
"hex games"
|
"hex games",
|
||||||
|
"wordle games"
|
||||||
]
|
]
|
||||||
for game_type in game_types:
|
for game_type in game_types:
|
||||||
self.bot.database[game_type].delete_many({})
|
self.bot.database[game_type].delete_many({})
|
||||||
|
@ -8,6 +8,7 @@ IMDbPY==2021.4.18
|
|||||||
lxml==4.8.0
|
lxml==4.8.0
|
||||||
Pillow==9.1.0
|
Pillow==9.1.0
|
||||||
pymongo==4.1.1
|
pymongo==4.1.1
|
||||||
|
pymongo[srv]
|
||||||
requests==2.27.1
|
requests==2.27.1
|
||||||
wolframalpha==5.0.0
|
wolframalpha==5.0.0
|
||||||
xmltodict==0.13.0
|
xmltodict==0.13.0
|
Reference in New Issue
Block a user