Soroush Bot
Official Soroush Plus Bot Library
This is the official library for developing bots on the Soroush Plus platform, developed by the Soroush Plus team with programming by Mahdi Ahmadi.
A comprehensive Python library for the Soroush Plus Bot API, designed with a clean and intuitive interface inspired by aiogram and rubka.
Features
- 🚀 Full API Coverage - All methods from the official Soroush Plus Bot API documentation
- 🎨 Intuitive Design - Clean and Pythonic interface similar to
aiogramandrubka - ⌨️ Rich Keyboard Support - Both inline and reply keyboards with easy builders
- 🔍 Powerful Filters - Command, text, media, chat type, and custom filters
- 📦 Complete Type System - Full type hints and data classes for all API objects
- 🔄 Long Polling & Webhooks - Support for both update retrieval methods
- 📁 File Upload Support - Upload photos, documents, audio, video, and more
- 🛡️ Error Handling - Comprehensive exception handling and error management
Installation
pip install aiohttp
Or install directly from source:
git clone https://github.com/yourusername/soroush-bot.git
cd soroush-bot
pip install -e .
Quick Start
import asyncio
from sorosh import Robot
from sorosh.filters import CommandFilter
from sorosh.types import ChatKeypadBuilder, InlineKeyboardBuilder
# Initialize your bot
TOKEN = "YOUR_BOT_TOKEN"
bot = Robot(TOKEN, show_progress=True)
# Build a keyboard
def make_main_menu():
kb = ChatKeypadBuilder(resize_keyboard=True)
kb.button("📸 Photo", id="photo")
kb.button("🎲 Dice", id="dice")
kb.row()
kb.button("ℹ️ Info", id="info")
return kb.build()
# Handle /start command
@bot.on_message(CommandFilter(["start", "help"]))
async def start_handler(bot, message):
inline_kb = InlineKeyboardBuilder()
inline_kb.callback("About", "about")
inline_kb.url("Website", "https://splus.ir")
await message.reply(
"👋 Welcome to the Soroush Plus Bot!\n\n"
"Use the buttons below to get started:",
chat_keypad=make_main_menu(),
inline_keypad=inline_kb.build()
)
# Handle callback queries
@bot.on_callback("about")
async def about_callback(bot, message):
await message.answer(
"🤖 Soroush Plus Bot\n"
"Built with Soroush Bot Library",
show_alert=True
)
# Default handler for unknown messages
@bot.default
async def default_handler(bot, message):
await message.reply(
"❌ I didn't understand that.\n"
"Use /help to see available commands.",
chat_keypad=make_main_menu()
)
# Run the bot
async def main():
await bot.run()
if __name__ == "__main__":
asyncio.run(main())
Table of Contents
- Installation
- Quick Start
- Core Components
- API Methods
- Working with Updates
- Keyboards
- Filters
- Types Reference
- Error Handling
- Examples
- Contributing
- License
Core Components
Bot / Robot
The Robot class (alias for Bot) is the main entry point for your bot.
from sorosh import Robot
bot = Robot(TOKEN, show_progress=True)
Parameters:
token(str): Your bot token from @botfathershow_progress(bool): Show progress messages in console (default: False)session(aiohttp.ClientSession): Custom session (optional)
Message
The Message class represents a message received from the API. It provides rich properties and methods for interacting with messages.
Key Properties:
text- Message textcaption- Media captionchat_id- Chat identifiersender_id- Sender identifiermessage_id- Message identifierfrom_user- Sender user objectchat- Chat objectis_command- Whether the message is a commandis_private- Whether the chat is privateis_group- Whether the chat is a groupis_photo- Whether the message contains a photois_video- Whether the message contains a videois_audio- Whether the message contains audiois_document- Whether the message contains a documentis_sticker- Whether the message contains a stickeris_voice- Whether the message contains a voice messageis_animation- Whether the message contains an animation (GIF)is_location- Whether the message contains a locationis_contact- Whether the message contains a contactis_dice- Whether the message contains a dicehas_media- Whether the message has any mediais_reply- Whether the message is a replyis_forwarded- Whether the message is forwardedis_edited- Whether the message is editedhas_link- Whether the text contains a linkis_hashtag- Whether the text contains a hashtagis_emoji- Whether the text contains emojisargs- Command arguments as a list
Filters
Filters are used to handle specific types of messages.
from sorosh.filters import CommandFilter, TextFilter, PrivateFilter, HasPhotoFilter
@bot.on_message(CommandFilter(["start"]))
async def start_handler(bot, message):
# Handle /start command
pass
@bot.on_message(TextFilter(r"^hello$"))
async def hello_handler(bot, message):
# Handle "hello" text
pass
@bot.on_message(PrivateFilter())
async def private_handler(bot, message):
# Handle messages from private chats
pass
@bot.on_message(HasPhotoFilter())
async def photo_handler(bot, message):
# Handle messages with photos
pass
Keyboards
Reply Keyboard (ChatKeypadBuilder)
Builds reply keyboards (buttons below the input field).
from sorosh.types import ChatKeypadBuilder
def make_keyboard():
kb = ChatKeypadBuilder(resize_keyboard=True)
kb.button("Button 1", id="btn1")
kb.button("Button 2", id="btn2")
kb.row()
kb.button("Button 3", id="btn3")
return kb.build()
# Usage
await message.reply("Choose an option:", chat_keypad=make_keyboard())
Inline Keyboard (InlineKeyboardBuilder)
Builds inline keyboards (buttons below the message).
from sorosh.types import InlineKeyboardBuilder
def make_inline_keyboard():
kb = InlineKeyboardBuilder()
kb.callback("Option 1", "opt1")
kb.callback("Option 2", "opt2")
kb.row()
kb.url("Website", "https://example.com")
return kb.build()
# Usage
await message.reply("Choose an option:", inline_keypad=make_inline_keyboard())
API Methods
Send Messages
send_message()
Send a text message.
await bot.send_message(
chat_id=message.chat_id,
text="Hello, world!",
parse_mode=None, # Optional: "HTML" or "Markdown"
chat_keypad=keyboard, # Reply keyboard
inline_keypad=inline_keyboard, # Inline keyboard
reply_to_message_id=message.message_id # Reply to specific message
)
Send Media
send_photo()
Send a photo from URL, bytes, or file_id.
# From URL
await bot.send_photo(
chat_id=message.chat_id,
photo="https://example.com/image.jpg",
caption="Beautiful photo!",
inline_keypad=keyboard
)
# From bytes
with open("image.jpg", "rb") as f:
image_data = f.read()
await bot.send_photo(
chat_id=message.chat_id,
photo=image_data,
caption="Uploaded photo!"
)
send_audio()
Send an audio file.
await bot.send_audio(
chat_id=message.chat_id,
audio="https://example.com/song.mp3",
caption="Check out this song!",
performer="Artist Name",
title="Song Title",
duration=180
)
send_document()
Send a document file.
await bot.send_document(
chat_id=message.chat_id,
document="https://example.com/document.pdf",
caption="Important document!"
)
send_video()
Send a video file.
await bot.send_video(
chat_id=message.chat_id,
video="https://example.com/video.mp4",
caption="Watch this video!",
duration=120,
width=1280,
height=720
)
send_animation()
Send an animation (GIF).
await bot.send_animation(
chat_id=message.chat_id,
animation="https://example.com/animation.gif",
caption="Funny GIF!"
)
send_voice()
Send a voice message.
await bot.send_voice(
chat_id=message.chat_id,
voice="https://example.com/voice.ogg",
caption="Voice message",
duration=30
)
send_video_note()
Send a video note (round video).
await bot.send_video_note(
chat_id=message.chat_id,
video_note="https://example.com/video_note.mp4",
duration=10,
length=240
)
Send Special Messages
send_location()
Send a location.
await bot.send_location(
chat_id=message.chat_id,
latitude=35.6892,
longitude=51.3890
)
send_contact()
Send a contact.
await bot.send_contact(
chat_id=message.chat_id,
phone_number="+989123456789",
first_name="John",
last_name="Doe"
)
send_dice()
Send a dice.
await bot.send_dice(
chat_id=message.chat_id,
emoji="🎲" # Optional: 🎲, 🎯, 🏀, ⚽, 🎳
)
send_sticker()
Send a sticker.
await bot.send_sticker(
chat_id=message.chat_id,
sticker="https://example.com/sticker.webp"
)
send_poll()
Send a poll.
await bot.send_poll(
chat_id=message.chat_id,
question="What is your favorite color?",
options=["Red", "Blue", "Green", "Yellow"],
type="Regular", # or "Quiz"
allows_multiple_answers=True,
is_anonymous=True,
correct_option_index=0, # For Quiz type
hint="Hint for the quiz"
)
Edit Messages
edit_message_text()
Edit a message's text.
await bot.edit_message_text(
text="New text",
chat_id=message.chat_id,
message_id=message.message_id
)
edit_message_caption()
Edit a message's caption.
await bot.edit_message_caption(
chat_id=message.chat_id,
message_id=message.message_id,
caption="New caption"
)
edit_message_reply_markup()
Edit a message's keyboard.
await bot.edit_message_reply_markup(
chat_id=message.chat_id,
message_id=message.message_id,
reply_markup=new_keyboard.to_dict()
)
Delete Messages
delete_message()
Delete a message.
await bot.delete_message(
chat_id=message.chat_id,
message_id=message.message_id
)
Reply Methods
The Message object provides convenient reply methods:
# Reply with text
await message.reply("Hello!")
# Reply with photo
await message.reply_image("https://example.com/image.jpg", caption="Photo!")
# Reply with location
await message.reply_location(35.6892, 51.3890)
# Reply with dice
await message.reply_dice("🎲")
# Reply with contact
await message.reply_contact("+989123456789", "John", "Doe")
# Reply with audio
await message.reply_audio("https://example.com/song.mp3")
# Reply with document
await message.reply_document("https://example.com/file.pdf")
# Reply with video
await message.reply_video("https://example.com/video.mp4")
# Reply with animation
await message.reply_animation("https://example.com/animation.gif")
# Reply with voice
await message.reply_voice("https://example.com/voice.ogg")
# Reply with sticker
await message.reply_sticker("https://example.com/sticker.webp")
# Edit the message
await message.edit("New text")
# Delete the message
await message.delete()
Working with Updates
Long Polling
Start the bot with long polling:
async def main():
await bot.run(timeout=30, limit=100)
if __name__ == "__main__":
asyncio.run(main())
Parameters:
timeout(int): Long polling timeout in seconds (default: 30)limit(int): Maximum number of updates to fetch (default: 100)
Webhooks
Set up a webhook:
await bot.set_webhook(
url="https://example.com/webhook",
max_connections=40,
allowed_updates=["message", "callback_query"]
)
Delete a webhook:
await bot.delete_webhook()
Get webhook info:
info = await bot.get_webhook_info()
print(info.url)
print(info.pending_update_count)
Filters
Command Filter
Handle specific commands:
@bot.on_message(CommandFilter(["start", "help"]))
async def handler(bot, message):
# Handles /start and /help
pass
Text Filter
Handle text matching a regex pattern:
@bot.on_message(TextFilter(r"^hello$"))
async def handler(bot, message):
# Handles exact "hello" text
pass
@bot.on_message(TextFilter(r"(hello|hi|hey)"))
async def handler(bot, message):
# Handles hello, hi, or hey
pass
Media Filters
Handle messages containing specific media types:
from sorosh.filters import (
HasPhotoFilter,
HasAudioFilter,
HasDocumentFilter,
HasVideoFilter,
HasAnimationFilter,
HasVoiceFilter,
HasStickerFilter,
HasLocationFilter,
HasContactFilter,
)
@bot.on_message(HasPhotoFilter())
async def photo_handler(bot, message):
# Handles messages with photos
pass
@bot.on_message(HasVideoFilter())
async def video_handler(bot, message):
# Handles messages with videos
pass
Chat Type Filters
Handle messages from specific chat types:
from sorosh.filters import PrivateFilter, GroupFilter, ChannelFilter
@bot.on_message(PrivateFilter())
async def private_handler(bot, message):
# Handles private chat messages
pass
@bot.on_message(GroupFilter())
async def group_handler(bot, message):
# Handles group messages
pass
Combining Filters
Combine multiple filters using AndFilter and OrFilter:
from sorosh.filters import AndFilter, OrFilter, CommandFilter, PrivateFilter
# Both conditions must be true
@bot.on_message(AndFilter(CommandFilter(["start"]), PrivateFilter()))
async def handler(bot, message):
# Handles /start in private chats only
pass
# At least one condition must be true
@bot.on_message(OrFilter(CommandFilter(["help"]), TextFilter(r"^help$")))
async def handler(bot, message):
# Handles /help or "help" text
pass
Types Reference
User
Represents a user or bot.
Properties:
id(int): Unique identifieris_bot(bool): Whether the user is a botfirst_name(str): First namelast_name(str, optional): Last nameusername(str, optional): Usernamelanguage_code(str, optional): Language codefull_name(str): Full name
Chat
Represents a chat.
Properties:
id(int): Unique identifiertype(str): Chat type ("private", "group", "supergroup", "channel")title(str, optional): Chat titleusername(str, optional): Usernamefirst_name(str, optional): First name (private chats)last_name(str, optional): Last name (private chats)
Update
Represents an incoming update.
Properties:
update_id(int): Unique update identifiermessage(Message, optional): New messageedited_message(Message, optional): Edited messagecallback_query(CallbackQuery, optional): Callback queryhas_message(bool): Whether update contains a messagehas_callback_query(bool): Whether update contains a callback query
CallbackQuery
Represents a callback query from an inline button.
Properties:
id(str): Unique identifierfrom_user(User): Sendermessage(Message, optional): Message with the inline buttondata(str, optional): Callback data
File
Represents a file ready for download.
Properties:
file_id(str): File identifierfile_unique_id(str): Unique file identifierfile_size(int, optional): File size in bytesfile_path(str, optional): File path
Methods:
get_download_url(token)→ Get download URL
Error Handling
The library provides specific exception types:
from sorosh.exceptions import SoroushAPIError, SoroushError, ValidationError, NetworkError
try:
await bot.send_message(chat_id, text)
except SoroushAPIError as e:
print(f"API Error: {e.message} (Code: {e.error_code})")
except SoroushError as e:
print(f"General Error: {e}")
except ValidationError as e:
print(f"Validation Error: {e}")
except NetworkError as e:
print(f"Network Error: {e}")
Common Error Codes
| Code | Description |
|---|---|
| 400 | Bad Request |
| 401 | Unauthorized (invalid token) |
| 403 | Forbidden |
| 404 | Not Found |
| 429 | Too Many Requests |
| 500 | Internal Server Error |
Examples
Basic Echo Bot
import asyncio
from sorosh import Robot
from sorosh.filters import CommandFilter, TextFilter
TOKEN = "YOUR_TOKEN"
bot = Robot(TOKEN, show_progress=True)
@bot.on_message(CommandFilter(["start"]))
async def start_handler(bot, message):
await message.reply("👋 Hello! Send me any message and I'll echo it back.")
@bot.on_message(TextFilter(r".*"))
async def echo_handler(bot, message):
await message.reply(f"🔊 {message.text}")
async def main():
await bot.run()
if __name__ == "__main__":
asyncio.run(main())
Admin Panel with Statistics
import asyncio
from sorosh import Robot
from sorosh.filters import CommandFilter, PrivateFilter, TextFilter
from sorosh.types import ChatKeypadBuilder
TOKEN = "YOUR_TOKEN"
ADMIN_IDS = ["your_admin_id_here"]
bot = Robot(TOKEN, show_progress=True)
user_stats = {}
def admin_keyboard():
kb = ChatKeypadBuilder(resize_keyboard=True)
kb.button("📊 Stats", id="stats")
kb.button("📢 Broadcast", id="broadcast")
kb.row()
kb.button("👥 Users", id="users")
kb.button("🗑️ Clear", id="clear")
return kb.build()
@bot.on_message(CommandFilter(["admin"]), PrivateFilter())
async def admin_handler(bot, message):
if message.sender_id not in ADMIN_IDS:
await message.reply("❌ Unauthorized!")
return
await message.reply(
"🔐 Admin Panel\n\n"
"Choose an option:",
chat_keypad=admin_keyboard()
)
@bot.on_message(TextFilter(r"^📊 Stats$"))
async def stats_handler(bot, message):
if message.sender_id not in ADMIN_IDS:
return
total_users = len(user_stats)
total_messages = sum(data.get("count", 0) for data in user_stats.values())
await message.reply(
f"📊 Statistics\n\n"
f"👥 Total Users: {total_users}\n"
f"📨 Total Messages: {total_messages}"
)
@bot.default
async def track_messages(bot, message):
if message.sender_id:
if message.sender_id not in user_stats:
user_stats[message.sender_id] = {"count": 0}
user_stats[message.sender_id]["count"] += 1
async def main():
await bot.run()
if __name__ == "__main__":
asyncio.run(main())
Poll Bot
import asyncio
from sorosh import Robot
from sorosh.filters import CommandFilter
from sorosh.types import InlineKeyboardBuilder
TOKEN = "YOUR_TOKEN"
bot = Robot(TOKEN, show_progress=True)
@bot.on_message(CommandFilter(["poll"]))
async def poll_handler(bot, message):
kb = InlineKeyboardBuilder()
kb.callback("📊 View Results", "poll_results")
await bot.send_poll(
chat_id=message.chat_id,
question="What is your favorite programming language?",
options=["Python", "JavaScript", "Go", "Rust", "Other"],
type="Regular",
allows_multiple_answers=False,
is_anonymous=True,
inline_keypad=kb.build(),
reply_to_message_id=message.message_id
)
@bot.on_callback("poll_results")
async def poll_results_callback(bot, message):
await message.answer(
"📊 Current Results\n\n"
"Python: 45%\n"
"JavaScript: 25%\n"
"Go: 15%\n"
"Rust: 10%\n"
"Other: 5%",
show_alert=True
)
async def main():
await bot.run()
if __name__ == "__main__":
asyncio.run(main())
Media Gallery Bot
import asyncio
import random
from sorosh import Robot
from sorosh.filters import CommandFilter, TextFilter
from sorosh.types import ChatKeypadBuilder, InlineKeyboardBuilder
TOKEN = "YOUR_TOKEN"
bot = Robot(TOKEN, show_progress=True)
# Sample images
IMAGES = [
"https://cdn.pixabay.com/photo/2024/01/15/10/00/cat-8509285_640.jpg",
"https://cdn.pixabay.com/photo/2023/12/16/11/23/dog-8452478_640.jpg",
"https://cdn.pixabay.com/photo/2023/12/11/16/35/bird-8444123_640.jpg",
]
def gallery_keyboard():
kb = ChatKeypadBuilder(resize_keyboard=True)
kb.button("🎲 Random Photo", id="random")
kb.button("📸 Next Photo", id="next")
return kb.build()
@bot.on_message(CommandFilter(["gallery"]))
async def gallery_handler(bot, message):
kb = InlineKeyboardBuilder()
kb.callback("❤️ Like", "like")
kb.callback("🔄 Next", "next_photo")
await bot.send_photo(
chat_id=message.chat_id,
photo=random.choice(IMAGES),
caption="📸 Random Photo\n\nClick the buttons below!",
inline_keypad=kb.build(),
chat_keypad=gallery_keyboard()
)
@bot.on_message(TextFilter(r"^🎲 Random Photo$"))
async def random_photo_handler(bot, message):
await gallery_handler(bot, message)
@bot.on_callback("like")
async def like_callback(bot, message):
await message.answer("❤️ Photo liked!", show_alert=False)
@bot.on_callback("next_photo")
async def next_photo_callback(bot, message):
await message.answer("🔄 Loading next photo...", show_alert=False)
await gallery_handler(bot, message)
async def main():
await bot.run()
if __name__ == "__main__":
asyncio.run(main())
Contributing
Contributions are welcome! Here's how you can help:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
# Clone the repository
git clone https://github.com/yourusername/soroush-bot.git
cd soroush-bot
# Create a virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -e .
pip install pytest pytest-asyncio
# Run tests
pytest
License
MIT License
Copyright (c) 2024 Soroush Bot Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Quick Reference
Common Methods
| Method | Description |
|---|---|
bot.run() |
Start long polling |
bot.send_message() |
Send text message |
bot.send_photo() |
Send photo |
bot.send_audio() |
Send audio file |
bot.send_document() |
Send document |
bot.send_video() |
Send video |
bot.send_animation() |
Send animation (GIF) |
bot.send_voice() |
Send voice message |
bot.send_sticker() |
Send sticker |
bot.send_location() |
Send location |
bot.send_contact() |
Send contact |
bot.send_dice() |
Send dice |
bot.send_poll() |
Send poll |
bot.edit_message_text() |
Edit message text |
bot.edit_message_caption() |
Edit message caption |
bot.delete_message() |
Delete message |
Message Reply Methods
| Method | Description |
|---|---|
message.reply() |
Reply with text |
message.reply_image() |
Reply with image |
message.reply_photo() |
Reply with photo |
message.reply_audio() |
Reply with audio |
message.reply_document() |
Reply with document |
message.reply_video() |
Reply with video |
message.reply_animation() |
Reply with animation |
message.reply_voice() |
Reply with voice |
message.reply_sticker() |
Reply with sticker |
message.reply_location() |
Reply with location |
message.reply_contact() |
Reply with contact |
message.reply_dice() |
Reply with dice |
message.reply_poll() |
Reply with poll |
message.edit() |
Edit message |
message.delete() |
Delete message |
Keyboard Builders
| Builder | Description |
|---|---|
ChatKeypadBuilder |
Build reply keyboards |
InlineKeyboardBuilder |
Build inline keyboards |
ReplyKeyboardRemove |
Remove reply keyboard |
Filters
| Filter | Description |
|---|---|
CommandFilter() |
Filter by command |
TextFilter() |
Filter by text pattern |
PrivateFilter() |
Filter private chats |
GroupFilter() |
Filter groups |
ChannelFilter() |
Filter channels |
HasPhotoFilter() |
Filter messages with photos |
HasAudioFilter() |
Filter messages with audio |
HasDocumentFilter() |
Filter messages with documents |
HasVideoFilter() |
Filter messages with videos |
HasAnimationFilter() |
Filter messages with animations |
HasVoiceFilter() |
Filter messages with voice |
HasStickerFilter() |
Filter messages with stickers |
HasLocationFilter() |
Filter messages with locations |
HasContactFilter() |
Filter messages with contacts |
AndFilter() |
Combine filters with AND |
OrFilter() |
Combine filters with OR |
Metadata
Release files for sorosh 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sorosh-1.0.0.tar.gz | 38.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sorosh-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 76.1 kB
Release files / sorosh-1.0.0.tar.gz
| Download URL | sorosh-1.0.0.tar.gz |
|---|---|
| Size | 38.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
407557122c84acb6d4bfc74b7478a20109ff9f163c4397153aa9328f06aaef01
|
|
BLAKE2b-256 checksum How to use checksums |
22314d2013366bac00d36f92a10d50e303120156da5032da002a3e850aaed73a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.11.4
|
Release files / sorosh-1.0.0-py3-none-any.whl
| Download URL | sorosh-1.0.0-py3-none-any.whl |
|---|---|
| Size | 37.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b8cefd43c5f529fc30720d1f222dc08d33bca8b7285259e4a4092c330154379b
|
|
BLAKE2b-256 checksum How to use checksums |
31ab8403ec0ef9eeef57c3b16293121a3a355dbf7f8bb4c1f16564b92ddc8e85
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.11.4
|