Ananasallowsyoutowritesimple(orcomplicated!)mastodonbotswithouthavingtorewriteconfigfileloading,interval-basedposting,scheduledposting,auto-replying,andsoon.
Somebotsareassimpleasaconfigurationfile:
[bepis]class=tracery.TraceryBotaccess_token=....grammar_file="bepis.json"Butit'seasytowriteonewithcustomizedbehavior:
classMyBot(ananas.PineappleBot):defstart(self):withopen('trivia.txt','r')astrivia_file:self.trivia=trivia_file.lines()@hourly(minute=17)defpost_trivia(self):self.mastodon.toot(random.choice(self.trivia))@replydefrespond_trivia(self,status,user):self.mastodon.toot("@{}:{}".format(user["acct"],random.choice(self.trivia)))Runmultiplebotsonmultipleinstancesoutofasingleconfigfile:
[jorts]class=custom.JortsBotdomain=botsin.spaceaccess_token=....line=632[roll]class=roll.DiceBotdomain=cybre.spaceaccess_token=....AndusetheDEFAULTsectiontosharecommonconfigurationoptionsbetweenthem:
[DEFAULT]domain=cybre.spaceclient_id=....client_secret=....GettingstartedpipinstallananasTheananaspippackagecomeswithascripttohelpyoumanageyourbots.
Simplygiveitaconfigfileandit'llloadyourbotsandclosethemsafelywhenitreceivesakeyboardinterrupt,SIGINT,SIGTERM,orSIGKILL.
ananasconfig.cfgIfyouhaven'tspecifiedaclientid/secretoraccesstoken,thescriptwillexitunlessyourunitwiththe--interactiveflag,whichallowsittopromptyoufortheinstancelogininformation.(Theonlypartoftheinputyouenterherethat'sstoredintheconfigfileistheinstancename--theemailandpasswordareonlyusedtogeneratetheaccesstoken).
ConfigurationThefollowingfieldsareinterpretedbythePineappleBotbaseclasssandwillworkforeverybot:
class:thefully-specifiedpythonclassthattherunnerscriptshouldinstantiatetostartyourbot.e.g."ananas.default.TraceryBot"
domain¹:thedomainoftheinstancetoruntheboton.Mustsupporthttpsconnections.Onlyincludethedomain,noprotocolorslashes.e.g."mastodon.social"
client_id¹,client_secret¹:thetokensthattheinstanceusestoidentifywhatclientthisbotispostingfrom/as.Willbeusedtodeterminewhat'sdisplayedunderneathallthepostsmadebythisbot.
access_token¹:theaccesstokenusedtoauthenticateAPIrequestswiththeinstance.Makesurethisissecret,don'tdistributeconfigfileswiththisfieldfilledoutorpeoplewillbeabletopostundertheaccountthistokenwascreatedwith.
admin:thefullusername(withoutleading@)oftheusertoDMerrorreportsto.Canbeleftunspecified,butisusefulforkeepinganeyeonthehealthofthebotwithoutconstantlymonitoringthescriptlogs.e.g.admin@example.town
¹:Filledoutautomaticallyifthebotisrunininteractivemode.
Additionalfieldsarespecifictothetypeofbot,refertothedocumentationforthebot'sclassformoreinformationaboutthefieldsitexpects.
WritingBotsCustombotclassesshouldbesubclassesofananas.PineappleBot.Ifyouoverride__init__,besuretocallthebaseclass's__init__.
DecoratorsInorderforthebottodoanything,youshouldaddamethoddecoratedwithatleastoneofthefollowingdecorators:
@ananas.reply:Callsthedecoratedfunctionwhenthebotismentionedbyanyotheruser.Decoratortakesnoparameters,butshouldonlybecalledonfunctionsmatchingthissignature:defreply_fn(self,mention,user).mentionwillbethedictionarycorrespondingtothestatuscontainingthemention(asreturnedbythemastodonAPI,userwillbethedictionarycorrespondingtotheuserthatmentionedthebot.
@ananas.interval(secs):Callsthedecoratedfunctioneverysecsseconds,startingwhenthebotisinitialized.Forintervalslongerthan~anhour,youmaywanttouse@scheduleinstead.e.g.@ananas.interval(60)
@ananas.schedule(**kwargs):Allowsyoutoschedule,cron-style,thedecoratedfunction.Acceptedkeywordsare"second","minute","hour","day_of_week"or"day_of_month"(butnotboth),"month",and"year".Ifanyofthesekeywordsarenotspecified,theywillbetreatedlikecrontreatsan*,thatis,aslongasthetimematchestheothervalues,anyvaluewillbeaccepted.Speakingofwhich,thecron-likesyntax"*"aswellas"*/3"arebothaccepted,andwillexpandtotheexpectedthing:forexample,schedule(hour="*/2",minute="*/10")willpostevery10minutesduringhourswhicharemultiplesof2.
@ananas.hourly(minute=0),@ananas.daily(hour=0,minute=0):Shortcutsfor@ananas.schedule()thatcallthedecoratedfunctiononceanhouratthespecifiedminuteoronceadayatthespecifiedhourandminute.Ifparametersareomittedthey'llpostatthetopofthehourormidnight(UTC).
@ananas.error_reporter:specifiescustombehaviorforreportingerrors.Thedecoratedfunctionshouldmatchthissignature:deferr(self,error)whereerrorisastringrepresentationoftheerror.
OverrideableFunctionsYoucanalsodefinethefollowingfunctionsandtheywillbecalledattherelevantpointsinthebot'slifecycle:
init(self):calledbeforetheconfigurationfilehasbeenloaded,sothatyoucansetdefaultvaluesforconfigfieldsincasetheconfigfiledoesn'tspecifythem.
start(self):calledafteralloftheinternalPineappleBotinitializationiscompleteandthemastodonAPIisreadytouse.Agoodplacetoloadfilesspecifiedintheconfig,postastartupnotice,orotherwisedobot-specificsetup.
stop(self):calledwhenthebothasreceivedashutdownsignalandneedstostop.Theconfigfilewillbesavedafterthis,soifyouneedtomakeanylastminutechangestotheconfig,dothathere.
ConfigurationFieldsAlloftheconfigurationfieldsforthecurrentbotareavailablethroughtheself.configobject,whichexposesthemwithbothfield-accessorsyntaxanddictionary-accessorsyntax,forexample:
foo=self.config.foobar=self.config["bar"]Thesecanberead(togettheuser'sconfigurationdata)orwrittento(toaffecttheconfigfileonnextsave)ordeleted(toremovethatfieldfromtheconfigfile).
Youcancallself.config.load()togetthelatestvaluesfromtheconfigfile.loadtakesanoptionalparametername,whichisthenameofthesectiontoloadintheconfigfileincaseyouwanttoloadadifferentonethanthebotwasstartedwith.
Youcanalsocallself.config.save()towriteanychangesmadesincethelastloadbacktotheconfigfile.
Notethatifyoucallself.config.load()duringbotoperation,withoutfirstcallingself.config.save(),youwilldiscardanychangesmadetotheconfigurationsincethelastload.
DistributingBotsYoucandistributebotshoweveryouwant;aslongastheclassisavailableinsomemoduleinpython'ssys.pathoramoduleaccessiblefromthecurrentdirectory,therunnerscriptwillbeabletoloadit.
Ifyouthinkyourbotmightbegenerallyusefultootherpeople,feelfreetocreateapullrequestonthisrepositorytogetitaddedtothecollectionofdefaultbots.
Questions?PingmeonMastodonat@chr@cybre.spaceorshootmeanemailatchr@cybre.spaceandI'llanswerasbestIcan!
评论