discord.py ბოტი Python პროცესია, რომელიც Discord-თან ერთ WebSocket კავშირს ინახავს ღიად. მისი ჰოსტინგი რთული არ არის, მაგრამ ის რამდენიმე კონკრეტული გზით ტყდება: იმავე import სახელით დაყენებული არასწორი ბიბლიოთეკა, privileged intent, რომელიც Developer Portal-ში არასოდეს ჩაგირთავს, extension, რომელიც ვერ ჩაიტვირთა და ბოტიც თან წაიღო, და - დიდი უპირატესობით ყველაზე გავრცელებული - სინქრონული გამოძახება, რომელიც event loop-ს ბლოკავს მანამ, სანამ Discord heartbeat-ზე უარს არ იტყვის.
ეს გიდი მოიცავს ბიბლიოთეკის ვერსიას, რაც requirements.txt-ში უნდა იყოს, როგორ ჯდება ერთმანეთთან setup_hook და cog-ები, როგორ დაასინქრონო application command tree rate limit-ის გარეშე და როგორი უნდა იყოს სერვერზე გაშვების ბრძანება. ბოტის მუდმივად გაშვებული შენახვის ზოგადი პრინციპები - მხოლოდ გამავალი კავშირი, შემომავალი port-ის გარეშე, გასვლისას restart - იგივეა, რაც Node ბოტისთვის, და აღწერილია როგორ გავუშვათ Discord ბოტი 24/7-ში. მეხსიერება და CPU კი აღწერილია რამდენი RAM და CPU სჭირდება Discord ბოტს-ში.
ბიბლიოთეკის არჩევა და Python-ის ვერსია#
სამ ბიბლიოთეკას ერთი import სახელი აქვს. import discord შეიძლება იყოს discord.py, py-cord ან PyPI-ზე არსებული ძველი discord stub პაკეტი, და pip სიამოვნებით დააყენებს რამდენიმეს ერთსა და იმავე გარემოში, სადაც ფაილები ერთმანეთს გადააწერენ და შეცდომებს აზრი ეკარგებათ.
| პაკეტი | Import | შენიშვნა |
|---|---|---|
discord.py | discord | ორიგინალი. ვერსია 2.x-ს აქვს app commands და cog-ები |
py-cord | discord | fork საკუთარი დეკორატორებით slash command-ებისთვის |
nextcord | nextcord | fork, რომელმაც სახელი შეიცვალა, ამიტომ თანაარსებობა შეუძლია |
discord | discord | stub, რომელიც discord.py-ს ეკიდება. ნუ დამოკიდებულობ |
თუ შენი import-ები უცნაური გახდა, გამოსავალია ყველას წაშლა და ზუსტად ერთის დაყენება:
$ pip uninstall -y discord.py py-cord discord$ pip install -U discord.pyორი API ურთიერთშემცვლელი არ არის. py-cord იყენებს @bot.slash_command-სა და discord.Option-ს; discord.py კი @bot.tree.command-ს, @app_commands.command-სა და discord.app_commands.describe-ს. ერთისთვის დაწერილი tutorial-იდან აღებული კოდი მეორეზე არ იმუშავებს, ხოლო მიღებული AttributeError გატეხილ ინსტალაციას ჰგავს და არა არასწორ ბიბლიოთეკას. გადაწყვიტე, რომელს იყენებ, დააფიქსირე და შეამოწმე, რომ ჩასმული ყველა ფრაგმენტი მას ემთხვევა.
Python-ის ვერსიაზე: discord.py 2.x-ს სჭირდება Python 3.8 ან უფრო ახალი. გამოიყენე 3.11 ან 3.12, თუ საპირისპირო მიზეზი არ გაქვს. Python 3.13-მა ამოიღო სტანდარტული ბიბლიოთეკის audioop მოდული, რომელზეც ხმის მხარდაჭერა იყო დამოკიდებული, ამიტომ discord.py-ის ძველი გამოშვებები 3.13-ზე იმპორტსაც ვერ ახერხებს, ახალი კი ამის ნაცვლად backport პაკეტს იყენებს. თუ შენი ბოტი აუდიოს უკრავს, ინტერპრეტატორის ვერსია გააზრებულად დააფიქსირე და გადასვლამდე ბიბლიოთეკის changelog წაიკითხე.
requirements.txt და სერვერზე ინსტალაცია#
სერვერის ინსტალაციას ინტერაქტიული shell არ აქვს და კითხვაზე პასუხის საშუალებაც არა. ყველაფერი, რაც მას სჭირდება, ერთ ფაილშია.
# example pins - use the versions you actually tested againstdiscord.py==2.5.2python-dotenv==1.1.1aiosqlite==0.21.0ვერსიები ==-ით ზუსტად დააფიქსირე. ალტერნატივა - ფიქსაციის გარეშე ან >= - ნიშნავს, რომ სერვერი დააყენებს იმას, რაც დილით გამოვიდა, და ბოტი, რომელიც ექვსი კვირაა არ შეცვლილა, restart-ზე ტყდება. ფიქსაცია შეცდომას განმეორებადსაც ხდის: იმავე ნაკრებს ლოკალურად დააყენებ და იგივე შეცდომას ნახავ. Python requirements და virtualenv-ები ფიქსაციის ინსტრუმენტებს სათანადოდ აღწერს; pip freeze უხეში საწყისი წერტილია, რადგან ის მთელ ტრანზიტულ ხეს წერს, იმასაც, რასაც შენ არ ითხოვდი.
application სერვერზე გაშვების ბრძანება ჩვეულებრივ ჯერ აყენებს და მერე უშვებს:
pip install --no-cache-dir -r requirements.txt && python -u bot.pyორი flag თავის ადგილს იმსახურებს. --no-cache-dir აჩერებს pip-ს wheel ქეშის შენახვისგან, რომელიც container-ზე უსარგებლოა, რადგან ის ისედაც ყოველ ჯერზე თავიდან აყენებს, ხოლო 5 GB გეგმაზე ეს ქეში დისკის რეალური ნაწილია. python -u stdout-ს ბუფერის გარეშე ხდის, რაც განსხვავებაა იმას შორის, რომ ლოგის ხაზებს კონსოლში მათი გაჩენისთანავე ხედავ და იმას შორის, რომ რამდენიმე წუთი არაფერს ხედავ, სანამ ბუფერი არ გაივსება. PYTHONUNBUFFERED=1 გარემოს ცვლადად იგივეს აკეთებს.
თუ ხმა გჭირდება, დააყენე discord.py[voice], რომელიც PyNaCl-ს ამატებს, და დარწმუნდი, რომ ffmpeg binary container-შია - ბიბლიოთეკა მას გარე პროცესად უშვებს და ვარდება დაკვრისას და არა import-ისას, ამიტომ ეს შეცდომაა, რომელსაც აუდიტორიის წინაშე აღმოაჩენ.
ბოტი, მისი intent-ები და setup_hook#
Intent-ები არის გამოწერა, რომელსაც კავშირის დამყარებისას აცხადებ. discord.Intents.default() გაძლევს ყველაფერს სამი privileged intent-ის გარდა, რომლებიც Developer Portal-ის Bot ჩანართზეც უნდა ჩართო და შენს კოდშიც.
import osimport discordfrom discord.ext import commandsintents = discord.Intents.default()intents.message_content = True # privileged: needed for prefix commandsintents.members = False # privileged: the expensive oneclass Bot(commands.Bot): def __init__(self): super().__init__(command_prefix="!", intents=intents) async def setup_hook(self): for extension in ("cogs.moderation", "cogs.levels"): await self.load_extension(extension)bot = Bot()bot.run(os.environ["DISCORD_TOKEN"], log_handler=None)setup_hook ერთხელ სრულდება, login-ის შემდეგ, მაგრამ ბოტის მზადყოფნამდე, და სწორედ აქ ეკუთვნის ასინქრონული გაშვების სამუშაო: extension-ების ჩატვირთვა, ბაზის pool-ის გახსნა, persistent view-ების რეგისტრაცია. ის ცვლის on_ready-ის შიგნით მუშაობის ჩვევას, რომელიც ისე არასწორია, რომ ადვილად გამოგრჩება - on_ready შეიძლება რამდენჯერმე ამოქმედდეს, რადგან ის ისევ ირთვება reconnect-ის შემდეგ, რომლის resume ვერ მოხერხდა. ყველაფერი, რასაც იქ აკეთებ, ორჯერ ხდება.
ორი intent დეტალი იწვევს დაბნეულობის უმეტესობას. თუ message_content გამორთულია, prefix ბრძანებები ჩუმად არაფერს აკეთებს, რადგან ბოტი შეტყობინების event-ს იღებს ცარიელი content ველით; discord.py გაშვებისას ლოგავს გაფრთხილებას დაკარგული privileged intent-ის შესახებ, და ეს გაფრთხილება არის მთელი პასუხი. თუ members ჩართულია, ბიბლიოთეკა კავშირისას ყოველი guild-ის წევრების სიას chunk-ებად ჩატვირთავს, რაც ნელია და მეხსიერებას ბევრს ითხოვს დიდ სერვერებში მყოფ ბოტზე - გადაეცი chunk_guilds_at_startup=False, თუ ქეში მართლა არ გჭირდება.
Cog-ები: ბოტის დაშლა მისი გატეხვის გარეშე#
cog არის კლასი, რომელიც აჯგუფებს ბრძანებებს, listener-ებსა და მდგომარეობას. extension კი მოდულია, რომელიც მას ტვირთავს. discord.py 2.x-ში მოდულის setup ფუნქციაც და load_extension-იც ასინქრონულია, რაც 1.x tutorial-ებისგან ყველაზე დიდი განსხვავებაა.
import discordfrom discord.ext import commandsfrom discord import app_commandsclass Moderation(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot @app_commands.command(description="Remove recent messages") @app_commands.describe(count="How many messages, 1-100") async def purge(self, interaction: discord.Interaction, count: int): await interaction.response.defer(ephemeral=True) deleted = await interaction.channel.purge(limit=count) await interaction.followup.send(f"Deleted {len(deleted)}.")async def setup(bot: commands.Bot): await bot.add_cog(Moderation(bot))პრაქტიკული წესები cog-ებისთვის სერვერზე:
- მარცხიანი extension ბოტს აჩერებს.
setup-ში აღძრული exceptionload_extension-იდან ვრცელდება, და თუ ეს გამოძახებაsetup_hook-შია და მის გარშემო არაფერია, პროცესი გადის. თუ ბოტმა ერთი გატეხილი cog უნდა გადაიტანოს, თითოეული ჩატვირთვაtry-ში გაახვიე და მარცხი ხმამაღლა ჩაწერე ლოგში, რათა ერთმა მოდულმა დანარჩენები არ ჩაატანოს. - Reload დეველოპმენტისთვისაა.
await bot.reload_extension("cogs.levels")მოდულს თავიდან ატვირთავს, რაც ლოკალურად გამოსადეგია და production-ზე ხაფანგია: ძველი ვერსიის მიერ შექმნილი ობიექტები ცოცხლდება listener-სა და task-ებში, რომლებიც არ გაგიწმენდია. სერვერზე ამის ნაცვლად გადატვირთე. - გაწმინდე `cog_unload`-ში. cog-ის მიერ დაწყებული
tasks.loopcog-ის წასვლის შემდეგაც გრძელდება, თუ იქ არ გააუქმებ. - Background loop-ებს სჭირდება `before_loop`. loop, რომელიც Discord-ს ელაპარაკება, ჯერ ბოტის მზადყოფნას უნდა დაელოდოს, თორემ მისი პირველი იტერაცია ქეშის გარეშე კლიენტზე გაივლის.
from discord.ext import tasks@tasks.loop(minutes=15)async def sweep(self): ...@sweep.before_loopasync def before_sweep(self): await self.bot.wait_until_ready()Slash command-ები და tree-ის სინქრონიზაცია#
application command-ები Discord-ის მხარეს ცხოვრობს. bot.tree შენს ლოკალურ განსაზღვრებებს ინახავს, sync კი მათ ტვირთავს. სანამ არ დაასინქრონებ, არაფერი ხდება, და სინქრონიზაცია deploy მოქმედებაა და არა გაშვების.
# Development: instant, one guildGUILD = discord.Object(id=123456789012345678)bot.tree.copy_global_to(guild=GUILD)await bot.tree.sync(guild=GUILD)# Production: global, propagates within about an hourawait bot.tree.sync()guild command-ები მაშინვე ჩნდება, ამიტომ ყველა tutorial მათ იყენებს. გლობალური command-ების გავრცელებას, დოკუმენტაციის მიხედვით, საათამდე სჭირდება, ამიტომ გლობალური სინქრონიზაციის შემდეგ ბრძანების არარსებობა ჩვეულებრივ უბრალოდ ადრეა. სინქრონიზაცია იმ scope-ის მთელ ნაკრებს ანაცვლებს, ამიტომ კოდიდან წაშლილი ბრძანება შემდეგ სინქრონიზაციაზე ქრება, ნაწილობრივი სია კი დანარჩენს წაშლის.
sync() არ გამოიძახო on_ready-შიც და setup_hook-შიც. Discord ზღუდავს, დღეში რამდენი command შეგიძლია შექმნა, და ბოტს, რომელიც ყოველ გაშვებაზე სინქრონიზირდება restart loop-ში, შეუძლია საკუთარი თავი დღის განმავლობაში დაბლოკოს. გავრცელებული ხერხი არის მფლობელისთვის განკუთვნილი prefix ბრძანება, რომელიც მოთხოვნით სინქრონიზირდება, ისე რომ deploy, რომელმაც command-ები არ შეცვალა, არაფერი ჯდება:
@bot.command()@commands.is_owner()async def sync(ctx): synced = await bot.tree.sync() await ctx.send(f"Synced {len(synced)} commands.")თუ სინქრონიზაცია validation შეცდომით ვარდება, წაიკითხე ველის გზა: სახელები უნდა იყოს პატარა ასოებით, 1-32 სიმბოლო, ჰარის გარეშე, ხოლო აღწერები 1-100 სიმბოლო. Hybrid command-ები - @commands.hybrid_command() - ერთი ფუნქციიდან ერთდროულად prefix და slash ბრძანებად რეგისტრირდება, რაც ორივეს მხარდაჭერის ყველაზე უმტკივნეულო გზაა ყველაფრის ორჯერ წერის გარეშე.
არასოდეს დაბლოკო event loop#
ეს განყოფილება production-ში ყველაზე მნიშვნელოვანია და ის უმეტეს tutorial-ს აკლია.
discord.py asyncio-ზე მუშაობს. ერთი thread, ერთი loop, რომელიც coroutine-ებს ერთმანეთში ალაგებს. ყოველი await არის წერტილი, სადაც loop-ს სხვა რამის გაკეთება შეუძლია, მათ შორის heartbeat-ის გაგზავნა, რომელიც gateway კავშირს ცოცხლად ინახავს. სინქრონულ გამოძახებას ასეთი წერტილი არ აქვს, ამიტომ სანამ ის მუშაობს, სხვა არაფერი მუშაობს - არც შენი დანარჩენი ბრძანებები და არც heartbeat.
სიმპტომია ბიბლიოთეკის ლოგის ხაზი:
WARNING discord.gateway Heartbeat blocked for more than 10 seconds.რასაც, თუ საკმარისად დიდხანს გაგრძელდა, მოჰყვება Discord-ის მიერ socket-ის დახურვა და ბოტის reconnect. მომხმარებლები ხედავენ ბოტს, რომელიც "შემთხვევით გადის ოფლაინში" სერვერზე, რომლის CPU გრაფიკიც კარგად გამოიყურება.
ჩვეულებრივი დამნაშავეები და რა გამოიყენო ამის ნაცვლად:
| დამბლოკავი | გამოიყენე სანაცვლოდ |
|---|---|
requests.get(...) | aiohttp, უკვე დამოკიდებულებაა |
time.sleep(5) | await asyncio.sleep(5) |
open(...).read() დიდ ფაილზე | await asyncio.to_thread(...) |
| სინქრონული ბაზის დრაივერი | asyncpg, aiosqlite, motor |
| სურათების დამუშავება, არქივირება, პარსინგი | await asyncio.to_thread(...) |
asyncio.to_thread (Python 3.9 და უფრო ახალი) ფუნქციას worker thread-ში უშვებს და შედეგს ელოდება, რაც ერთხაზიანი გამოსავალია CPU-ზე მსუბუქი, მაგრამ ნელი სამუშაოსთვის. მართლაც CPU-ზე მძიმე სამუშაო ბოტზე საერთოდ არ უნდა იყოს; გადაიტანე queue-სა და ცალკე worker-ში, როგორც ფონური დავალებები პატარა სერვერზე-შია.
იგივე დისციპლინა ეხება rate limit-ებს. discord.py 429-ებს თავად ამუშავებს retry_after-ის ლოდინით, მაგრამ loop, რომელიც ყოველ წამს შეტყობინებას ცვლის, ან ბრძანება, რომელიც ერთბაშად ორმოცდაათ შეტყობინებას აგზავნის, დროის უმეტეს ნაწილს ლოდინში გაატარებს. შეაჯგუფე, რასაც შეძლებ, და API გამოძახება არასოდეს ჩასვა წევრთა სიაზე მჭიდრო loop-ში.
ჰოსტზე გაშვება#
Python ბოტის დეპლოის ფორმა ნებისმიერი გრძელვადიანი პროცესისას ჰგავს: მიიღე კოდი, დააყენე დამოკიდებულებები, გაუშვი ერთი ბრძანება, გადატვირთე, როცა ის გავა.
გარემოს ცვლადები Startup ჩანართზე იდება და არა რეპოზიტორიაში - token, ბაზის URL, guild ID, რომელზეც სინქრონიზირდები. წაიკითხე ისინი მკაცრი წარუმატებლობით, თუ არ არსებობს, რადგან discord.LoginFailure ჩატვირთვიდან სამ წამში ბევრად უარესი შეტყობინებაა, ვიდრე "DISCORD_TOKEN is not set". ლოკალურად python-dotenv იგივე სახელებს კითხულობს .env ფაილიდან, რომელიც .gitignore-შია. სად ჩავდოთ საიდუმლოებები application სერვერზე უფრო გრძელი არგუმენტია.
RE:NODE-ზე Git ინტეგრაცია მხოლოდ GitHub-ისთვისაა, GitHub App-ის მეშვეობით, რომელიც მოკლევადიან token-ებს გასცემს, ამიტომ კერძო რეპოზიტორიები მუშაობს სერვერზე credential-ის შენახვის გარეშე. ორი გადამრთველი მას მართავს: ბრენჩის pull ყოველ გაშვებაზე და deploy on push, რომელიც სერვერს გადატვირთავს, როცა GitHub ამ ბრენჩზე push-ს ატყობინებს - და მხოლოდ მაშინ, როცა ის უკვე გაშვებული იყო. ყოველი დეპლოი ერთი ჩანაწერია, ამიტომ განასხვავებ დეპლოის, რომელიც გავიდა, იმისგან, რომელიც ჩავარდა. იგივე პროცესი Node-სთვის განხილულია Node.js აპლიკაციის დეპლოი GitHub-იდან-ში და Python-ზე იდენტურად მუშაობს.
ლოგირებისთვის გამოიძახე discord.utils.setup_logging() ან logging თავად გაწყე, და თუ საკუთარ კონფიგურაციას აკეთებ, bot.run-ს გადაეცი log_handler=None, თორემ ბიბლიოთეკა მეორე handler-ს დააყენებს და ყოველი ხაზი ორჯერ გამოჩნდება. ლოგი stdout-ში ჩაწერე, რომ კონსოლმა ის პირდაპირ აჩვენოს; container-ის შიგნით ლოგ ფაილი ფაილია, რომლის წამოსაღებადაც SFTP-ით უნდა წახვიდე, და ვისაც არავინ ბრუნავს, რამდენიმე თვეში დისკს ავსებს. ლოგები, რომლებიც ღირს შენახვად იმაზეა, რა უნდა მოხვდეს მასში.
ჰოსტინგის ორი ქცევა ღირს იმის ცოდნად, სანამ გაგაკვირვებს. თუ პროცესი container-ის მეხსიერების ლიმიტს მიაღწევს, ის ჩერდება და სუფთად გადაიტვირთება და არა swap-ზე გადადის, ამიტომ შეუზღუდავი ქეში ჩნდება როგორც restart რამდენიმე საათში ერთხელ და არა როგორც შენელება. და crash loop შეიმჩნევა: საათში სამი მოულოდნელი restart სერვერის გვერდზე გაფრთხილებას აჩენს და ავტომატურად ტიკეტს ხსნის, ექვსი კი შეჩერებამდე მიდის. ორივე აღწერილია დიაგნოსტიკური მხრიდან რატომ იტვირთება შენი სერვერი განუწყვეტლივ-ში.
Sharding და როდის გჭირდება#
Discord ბოტს shard-ებად დაყოფას 2,500 guild-ზე მეტისას ავალდებულებს. ამის ქვემოთ ის პრობლემის გადაწყვეტაა, რომელიც არ გაქვს.
როცა იქამდე მიაღწევ, discord.py ამას ამარტივებს: commands.AutoShardedBot ცვლის commands.Bot-ს და ყველა shard-ს ერთ event loop-ზე, ერთ პროცესში უშვებს. gateway-ს ეკითხებიან, რამდენი shard სურს, ქეშები გაზიარებულია და მეხსიერება მონაცემთა რაოდენობასთან ერთად იზრდება და არა shard-ების რაოდენობასთან. ეს არსებითად განსხვავებული სიტუაციაა discord.js-თან შედარებით, რომელიც ყოველ shard-ზე პროცესს ქმნის და საბაზისო მეხსიერებასაც ამრავლებს.
bot = commands.AutoShardedBot(command_prefix="!", intents=intents)რაც იცვლება, ისაა, რომ ერთი დაბლოკილი coroutine ახლა ყველა shard-ზე მოქმედებს, ამიტომ წინა განყოფილების event loop-ის დისციპლინა რჩევიდან მოთხოვნად იქცევა. დიდი ბოტები gateway-გან უფრო მაღალ max_concurrency-საც იღებენ, რაც რამდენიმე shard-ს ერთდროულად identify-ის საშუალებას აძლევს და სრულ restart-ს ბევრად სწრაფს ხდის. რაზე გაქვს უფლება, შეამოწმე ავთენტიფიცირებული GET /gateway/bot-ით და არ გამოიცნო.
პრობლემების მოგვარება#
`ModuleNotFoundError` ბიბლიოთეკის დამატების შემდეგ. ლოკალურად დაყენებულია და requirements.txt-ში აკლია. სერვერი მხოლოდ იმას აყენებს, რასაც ფაილი ამბობს.
`PrivilegedIntentsRequired` გაშვებისას. privileged intent კოდშია, მაგრამ Developer Portal-ში არა.
`LoginFailure: Improper token has been passed`. ცარიელი ან არასწორი DISCORD_TOKEN, გადატვირთული token, რომელიც არასოდეს გადაგიდეპლოებია, ან ბოტის token-ის ნაცვლად ჩასმული client secret.
Prefix ბრძანებები არაფერს აკეთებს, slash ბრძანებები მუშაობს. message_content intent. ბოტი event-ს ცარიელი კონტენტით იღებს.
`CommandNotFound` slash ბრძანებაზე, რომელიც არსებობს. ის არასოდეს დასინქრონებულა, ან სხვა guild-ზე დასინქრონდა, ან გლობალური სინქრონიზაცია ჯერ არ გავრცელებულა.
`AttributeError: 'Bot' object has no attribute 'slash_command'`. py-cord-ის კოდი discord.py-ზე მუშაობს. აირჩიე ერთი ბიბლიოთეკა.
ბოტი წუთობით წყვეტს პასუხს. დაბლოკილი event loop. ლოგში მოძებნე heartbeat გაფრთხილება და შეხედე, რა გაეშვა მის წინ.
`RuntimeError: Event loop is closed` Windows-ზე გამორთვისას. ლოკალური დეველოპმენტის შეწუხება proactor event loop-იდან; ის არ ხდება Linux container-ზე, სადაც ბოტი ჰოსტდება.
FAQ#
გამოვიყენო discord.py თუ py-cord?
discord.py ისევ აქტიურად მხარდაჭერილია და ის არის, რასაც უმეტესი მიმდინარე დოკუმენტაცია და პასუხი ეხება, ამიტომ ის უფრო უსაფრთხო ნაგულისხმევია. py-cord გონივრული არჩევანია, თუ მისი slash command სინტაქსი გირჩევნია. რაც არჩევანი არ არის, მათი შერევაა: ისინი ერთ import სახელით ინსტალირდება და ერთმანეთს ტეხს.
მჭირდება თუ არა virtualenv ჰოსტინგის მხარეს?
მართულ application სერვერზე ყოველი container უკვე გამოყოფს შენს დამოკიდებულებებს, ამიტომ მის შიგნით virtualenv საქაღალდეს ამატებს და თითქმის არაფერს. გამოიყენე ლოკალურად, სადაც რამდენიმე პროექტი ერთ ინტერპრეტატორს იზიარებს, და requirements.txt დააფიქსირე, რომ ორივე გარემომ ერთი და იგივე დააყენოს.
სად ჩავდო ბოტის token?
Startup ჩანართზე გარემოს ცვლადში, os.environ["DISCORD_TOKEN"]-ით წაკითხული. არა კოდში, არა დაკომიტებულ .env-ში, არა კონფიგურაციის ფაილში რეპოზიტორიაში. თუ უკვე push-ი გაკეთდა, გადატვირთე Developer Portal-ში - შეცვლა ერთადერთი გამოსავალია, რადგან git ისტორია ძველ მნიშვნელობას ინახავს.
რატომ ნელდება ჩემი ბოტი, რაც მეტი ადამიანი იყენებს?
ჩვეულებრივ, ერთი დამბლოკავი გამოძახება პოპულარულ ბრძანებაში. ყოველი მომხმარებელი, რომელიც ამ გამოძახებას ელოდება, იმავე event loop-ს ელოდება. იპოვე სინქრონული ბიბლიოთეკის გამოძახება, გადაიტანე asyncio.to_thread-ში ან async კლიენტზე და პრობლემა მეტი CPU-ს გარეშე გაქრება.
შემიძლია ბოტისა და პატარა ვებ დაშბორდის ერთ სერვერზე გაშვება?
დიახ, თუ მათ ერთ პროცესში ინახავ async ვებ framework-ით, ან შეგიძლია ორი პროცესი, რომლებიც ერთ მეხსიერების ლიმიტსა და ერთ CPU წილს იზიარებენ. ცალკე პატარა გეგმა გრაფიკებს ამოსაკითხად ტოვებს და აჩერებს მოწყვეტილ დაშბორდს, რომ ბოტი არ წაიყოლოს. ვებ მხარისთვის FastAPI-ის ან Flask-ის დეპლოი დეტალებს შეიცავს.
როგორ გავიგო, რომ ბოტი მართლა მუშაობს?
gateway ბოტს გამოსაკითხი endpoint არ აქვს, ამიტომ წაიკითხე კონსოლი, რომელიც ცოცხალ, გაუფილტრავ გამოტანას და მეხსიერებისა და CPU-ს გრაფიკებს ლიმიტების ფონზე აჩვენებს. ჩაწერე ერთი ხაზი ready-ზე guild-ების რაოდენობით და ერთი, როცა ბრძანება ჩავარდება. როგორ წავიკითხოთ სერვერის კონსოლი გამოცნობის გარეშე გვასწავლის, გაშვების ხმაური რეალური გაუმართაობისგან განვასხვავოთ.




კომენტარები
სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.