add 1 another directory
This commit is contained in:
parent
5fc3884101
commit
6d029d1d01
26
delibird-master/LICENSE
Executable file
26
delibird-master/LICENSE
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
Copyright (c) The Regents of the University of California.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
3. Neither the name of the University nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
21
delibird-master/README.md
Executable file
21
delibird-master/README.md
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
# Delibird / Birb-bot
|
||||||
|
|
||||||
|
Delibird / Birb-bot is a Mastodon bot flying from user to user!
|
||||||
|
|
||||||
|
It can be sent by an user to another by mentioning the bird with a message like
|
||||||
|
“Go see user@domain” or with similar keywords. It can only be at one place at a
|
||||||
|
time, though, meaning that when it is flying away to someone or it is visiting
|
||||||
|
someone else than you, you cannot send it flying to another user!
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Python 3
|
||||||
|
- Mastodon.py: https://github.com/halcy/Mastodon.py/
|
||||||
|
|
||||||
|
## Writing your own birbs
|
||||||
|
|
||||||
|
To avoid spoilers around the “rewards” posted by my own birb-bot, the repo does
|
||||||
|
not include the full “data.py” I use.
|
||||||
|
|
||||||
|
Instead, you can find a “data.py.example” which is pretty close but omits some
|
||||||
|
rewards and media definitions.
|
394
delibird-master/data.py.example
Executable file
394
delibird-master/data.py.example
Executable file
|
@ -0,0 +1,394 @@
|
||||||
|
MEDIA = {
|
||||||
|
'FLYING_AWAY': {
|
||||||
|
'file': 'bye.mp4',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=6Qp4wafJ8_I',
|
||||||
|
},
|
||||||
|
'FLYING_IN': {
|
||||||
|
'file': 'hello.mp4',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=Gu8EudiPLZw',
|
||||||
|
},
|
||||||
|
'STAIRS': {
|
||||||
|
'file': 'stairs.mp4',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=coDxZZjTt4w',
|
||||||
|
},
|
||||||
|
'BELLY_RUB': {
|
||||||
|
'file': 'rewards/belly-rub.mp4',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=-bTaj37E79k',
|
||||||
|
},
|
||||||
|
'DJBIRB': {
|
||||||
|
'file': 'rewards/dj.mp4',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=1v2SN06DnPo',
|
||||||
|
},
|
||||||
|
'SHOPPING': {
|
||||||
|
'file': 'rewards/shopping.mp4',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=BfhlPEkNm7o',
|
||||||
|
},
|
||||||
|
'DANCINGQUEEN': {
|
||||||
|
'file': 'rewards/queen.mp4',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=9DaGgzJm6is',
|
||||||
|
},
|
||||||
|
'MICROWAVE': {
|
||||||
|
'file': 'rewards/microwave.mp4',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=NVY0qNe3BUE',
|
||||||
|
},
|
||||||
|
'JUMPING': {
|
||||||
|
'file': 'rewards/jumping.mp4',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=_62o7u-BYRA',
|
||||||
|
},
|
||||||
|
'RIDING': {
|
||||||
|
'file': 'rewards/riding.mp4',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=dVngVDTYbOQ',
|
||||||
|
},
|
||||||
|
'RIVERDANCE': {
|
||||||
|
'file': 'rewards/riverdance.webm',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=KF7gk10jgEw',
|
||||||
|
},
|
||||||
|
'PAPERTOWEL': {
|
||||||
|
'file': 'rewards/papertowel.mp4',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=6_-4KArFR08',
|
||||||
|
},
|
||||||
|
'BIRDVSPHONE': {
|
||||||
|
'file': 'rewards/phonecover.mp4',
|
||||||
|
'source': 'https://www.youtube.com/watch?v=ktzfC5ZcOtE',
|
||||||
|
},
|
||||||
|
'KILLERBIRD': {
|
||||||
|
'file': 'rewards/killerbird.mp4',
|
||||||
|
'source': 'https://twitter.com/colloritz/status/798470429308424192',
|
||||||
|
},
|
||||||
|
'WITHFRIENDS': {
|
||||||
|
'file': 'rewards/friends.mp4',
|
||||||
|
'source': 'https://twitter.com/s_hatachan/status/879946102878883840',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
MSGS = {
|
||||||
|
'ERROR_NOBOT': {
|
||||||
|
'text': '''@{sender_acct} Pwii! Pwi.
|
||||||
|
|
||||||
|
[FR] Désolé, je n'irai pas voir {receiver_acct}, car cette personne ne semble pas vouloir de la visite de bots.
|
||||||
|
|
||||||
|
[EN] Sorry, I won't visit {receiver_acct} as they do not seem to like bots very much.''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'ERROR_UNDELIVERABLE': {
|
||||||
|
'text': '''@{sender_acct} Pwiii…
|
||||||
|
|
||||||
|
[FR] Désolé, je n'ai pas réussi à joindre {receiver_acct}… essaie avec quelqu'un d'autre ?
|
||||||
|
|
||||||
|
[EN] Sorry, I can't reach {receiver_acct}… maybe try with someone else?''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'ERROR_OWNED': {
|
||||||
|
'text': '''@{sender_acct} Pwi pwi pwi!!!
|
||||||
|
|
||||||
|
[FR] Je suis chez quelqu'un d'autre !
|
||||||
|
Je serai de nouveau disponible si cette personne ne m'envoie pas me promener d'ici {hours}h{minutes:02d} !
|
||||||
|
Tu peux me demander de te dire quand je serai disponible en me disant « notifie moi » !
|
||||||
|
|
||||||
|
[EN] I'm currently visiting someone else!
|
||||||
|
I'll be available in {hours}h{minutes:02d} if that person doesn't send me around in the meantime!
|
||||||
|
You can request that I notify you when I'm available by telling me “notify me”!''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'ERROR_OWNED2': {
|
||||||
|
'text': '''@{sender_acct} Pwi pwi pwi!!!
|
||||||
|
|
||||||
|
[FR] Je suis chez quelqu'un d'autre !
|
||||||
|
Je serai de nouveau disponible si cette personne ne m'envoie pas me promener d'ici {hours}h{minutes:02d} !
|
||||||
|
Je te dirai la prochaine fois que je serai disponible !
|
||||||
|
|
||||||
|
[EN] I'm currently visiting someone else!
|
||||||
|
I'll be available in {hours}h{minutes:02d} if that person doesn't send me around in the meantime!
|
||||||
|
I'll tell you next time I'm available!''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'ERROR_DELIVERY': {
|
||||||
|
'text': '''@{sender_acct} Pwiii pwii pwii pwiii!
|
||||||
|
|
||||||
|
[FR] Je suis déjà en train de m'envoler pour voir quelqu'un d'autre !
|
||||||
|
Tu peux me demander de te dire quand je serai disponible en me disant « notifie moi » !
|
||||||
|
|
||||||
|
[EN] I'm already flying away to someone else!
|
||||||
|
You can request that I notify you when I'm available by telling me “notify me”!''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'ERROR_DELIVERY2': {
|
||||||
|
'text': '''@{sender_acct} Pwiii pwii pwii pwiii!
|
||||||
|
|
||||||
|
[FR] Tu m'as déjà envoyé voir quelqu'un d'autre ! Tu peux encore revenir sur ce choix en me disant « reviens » !
|
||||||
|
|
||||||
|
[EN] You already sent me to someone else! If that was a mistake, you can tell me to “come back”!''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'ERROR_DELIVERY3': {
|
||||||
|
'text': '''@{sender_acct} Pwiii pwii pwii pwiii!
|
||||||
|
|
||||||
|
[FR] Je suis déjà en train de m'envoler pour voir quelqu'un d'autre !
|
||||||
|
Je te dirai la prochaine fois que je serai disponible !
|
||||||
|
|
||||||
|
[EN] I'm already flying away to someone else!
|
||||||
|
I'll tell you next time I'm available!''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'ERROR_INVALID_FORMAT': {
|
||||||
|
'text': '''@{sender_acct} Pwii? Pwiii!
|
||||||
|
|
||||||
|
[FR] Je n'ai pas compris vers qui je dois m'envoler ? Il faut me dire « va voir utilisateur@domaine » !
|
||||||
|
|
||||||
|
[EN] I don't understand who I'm supposed to be flying to? You have to tell me “go see user@domain”!''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'ERROR_UNKNOWN_ACCOUNT': {
|
||||||
|
'text': '''@{sender_acct} Pwiii! Pwiii?
|
||||||
|
|
||||||
|
[FR] Je n'ai pas trouvé {acct} ! Vers qui d'autre dois-je aller ?
|
||||||
|
|
||||||
|
[EN] I can't find {acct}! Who else should I visit?''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'ERROR_UNKNOWN_ACCOUNT2': {
|
||||||
|
'text': '''@{sender_acct} Pwiii! Pwiii?
|
||||||
|
|
||||||
|
[FR] Je n'ai pas trouvé {acct} ! Vers qui d'autre dois-je aller ? Peut-être voulais-tu dire {suggested_acct} ?
|
||||||
|
|
||||||
|
[EN] I can't find {acct}! Who else should I visit? Maybe you meant {suggested_acct}?''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'ERROR_INTERNAL': {
|
||||||
|
'text': '''@{sender_acct} Pwiiii…
|
||||||
|
|
||||||
|
[FR] Je suis désolé, je n'arrive pas à contacter {acct}… il va falloir essayer avec quelqu'un d'autre.
|
||||||
|
|
||||||
|
[EN] I'm sorry I cannot reach {acct}… who should I be flying to?''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'ERROR_SAME_ACCOUNT': {
|
||||||
|
'text': '''@{sender_acct} Pwiii!
|
||||||
|
|
||||||
|
[FR] Hé, ce n'est pas du jeu de vouloir me garder comme ça ! Vers qui d'autre dois-je aller ?
|
||||||
|
|
||||||
|
[EN] Hey, it's not fair to try to keep me like this! Who else should I visit?''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'ERROR_OWN_ACCOUNT': {
|
||||||
|
'text': '''@{sender_acct} Pwii. Pwii?
|
||||||
|
|
||||||
|
[FR] Je ne peux pas aller me voir moi-même ?
|
||||||
|
|
||||||
|
[EN] I cannot fly to see myself?''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'DELIVERY_START': {
|
||||||
|
'text': '''@{sender_acct} Pwiipwii pwiipwiii!
|
||||||
|
|
||||||
|
[FR] En route vers chez {acct} ! Ça peut me prendre un peu de temps !
|
||||||
|
Si je me suis trompé de personne, tu peux me dire de revenir en me disant « reviens » !
|
||||||
|
|
||||||
|
[EN] On my way to {acct}! This may take me a while!
|
||||||
|
If I'm not flying to the correct person, you can still tell me to “come back”!''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
'media': ['FLYING_AWAY'],
|
||||||
|
},
|
||||||
|
'DELIVERY_CANCELLED': {
|
||||||
|
'text': '''@{sender_acct} Pwiii…
|
||||||
|
|
||||||
|
[FR] Tu ne veux plus que j'aille voir {acct} ? D'accord… qui dois-je aller voir ?
|
||||||
|
|
||||||
|
[EN] You don't want me to go visit {acct} anymore? Ok… but who should I fly to?''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'DELIVERED': {
|
||||||
|
'text': '''@{receiver_acct} :caique: Pwii pwiii! :caique:
|
||||||
|
|
||||||
|
[FR] Bonjour de la part de {sender_acct} ! Je ne suis qu'à toi pour les {nb_hours} prochaines heures ! Envoie-moi voir qui tu veux en répondant « va voir utilisateur@domaine » !
|
||||||
|
Tu peux aussi me dire « va jouer ailleurs » !
|
||||||
|
|
||||||
|
[EN] Hello from {sender_acct_short}! Send me to anyone by replying “go see user@domain”! Until then, I'll be yours for the next {nb_hours} hours!
|
||||||
|
You can also tell me to “go play somewhere else”!''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
'media': {('FLYING_IN',): 3, ('STAIRS',): 1},
|
||||||
|
},
|
||||||
|
'IDLE': {
|
||||||
|
'text': ''':caique: Pwiii… pwii pwii! :caique:
|
||||||
|
|
||||||
|
[FR] Personne ne m'a envoyé me promener depuis un moment… du coup, n'importe qui peut le faire en me répondant « va voir utilisateur@domaine » !
|
||||||
|
|
||||||
|
[EN] Noone sent me flying away… but now, anybody can! Reply me with “go see user@domain”, and I'll fly to them!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
},
|
||||||
|
'IDLE2': {
|
||||||
|
'text': ''':caique: Pwiii… pwii pwii! :caique:
|
||||||
|
|
||||||
|
[FR] La personne chez qui j'ai été envoyé préfère que j'aille jouer ailleurs ! Du coup, envoyez-moi chez qui vous voulez en me répondant « va voir utilisateur@domaine » !
|
||||||
|
|
||||||
|
[EN] The person I was sent to would like me to play somewhere else! Reply me with “go see user@domain”, and I'll fly to them!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
},
|
||||||
|
'NOTIFY_REQUEST': {
|
||||||
|
'text': '''@{acct} :caique: Pwii! :caique:
|
||||||
|
|
||||||
|
[FR] Entendu ! Je te dirai la prochaine fois que je serai disponible !
|
||||||
|
|
||||||
|
[EN] Got it! I'll tell you next time I'm available!''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'NOTIFY_IDLE': {
|
||||||
|
'text': '''@{acct} :caique: Pwii pwii! :caique:
|
||||||
|
|
||||||
|
[FR] Tu m'avais demandé de te notifier quand je deviendrai disponible ! Et bien c'est chose faite !
|
||||||
|
|
||||||
|
[EN] You requested that I notify you when I become available! I'm available now!''',
|
||||||
|
'privacy': 'direct',
|
||||||
|
},
|
||||||
|
'BELLY_RUB': {
|
||||||
|
'text': ''':caique: Pwiiiiii <3 :caique:
|
||||||
|
|
||||||
|
[FR] J'ai rencontré {nb_users} personnes et j'ai apporté du bonheur {nb_likes} fois ! J'ai bien mérité un petit gratouilli !
|
||||||
|
|
||||||
|
[EN] I've met {nb_users} people and I've made them happy {nb_likes} times! I've earned myself a belly rub!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
'media': ['BELLY_RUB'],
|
||||||
|
},
|
||||||
|
'PAPERTOWEL': {
|
||||||
|
'text': ''':caique: Pwiii… pwii! :caique:
|
||||||
|
|
||||||
|
[FR] Des fois, je comprends vraiment pas mon cousin à tête noire… mais bon, il a l'air de bien s'amuser !
|
||||||
|
|
||||||
|
[EN] Sometimes, I really don't understand my black-headed cousin… but he seems to be having a lot of fun!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
'media': ['PAPERTOWEL'],
|
||||||
|
},
|
||||||
|
'JUMPING': {
|
||||||
|
'text': ''':caique: Pwii? Pwiii! :caique:
|
||||||
|
|
||||||
|
[FR] Mon cousin à tête noir commence à sauter sur place… sinon, j'ai rencontré {nb_users} d'entre vous et je leur ai fait plaisir {nb_likes} fois !
|
||||||
|
|
||||||
|
[EN] My black-headed cousin is starting to jump in-place… by the way, I've met {nb_users} of you and I've made them happy {nb_likes} times!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
'media': ['JUMPING'],
|
||||||
|
},
|
||||||
|
'DANCINGQUEEN': {
|
||||||
|
'text': ''':caique: Pwii~ pwiii~ pwiii~ 🎶 :caique:
|
||||||
|
|
||||||
|
[FR] Je vais bientôt rencontrer tout le fediverse ! {nb_users} personnes ! Pfiou ! Sinon, j'aime bien Queen !
|
||||||
|
|
||||||
|
[EN] I've met almost all of the fediverse! {nb_users} of you! Wow! Also, I like Queen!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
'media': ['DANCINGQUEEN'],
|
||||||
|
},
|
||||||
|
'SHOPPING': {
|
||||||
|
'text': ''':caique: Pwi pwi pwiiii! :caique:
|
||||||
|
|
||||||
|
[FR] J'ai rencontré pas moins de {nb_users} mastonautes ! Et ils ont aimé mes visites {nb_likes} fois ! Mon cousin est parti m'acheter à manger !
|
||||||
|
|
||||||
|
[EN] I've met {nb_users} mastonauts! And they liked my visits {nb_likes} times! My cousin went shopping for dinner!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
'media': ['SHOPPING'],
|
||||||
|
},
|
||||||
|
'MICROWAVE': {
|
||||||
|
'text': ''':caique: Pwi! Pwi! Pwi! Pwi! Pwi! Pwi! :caique:
|
||||||
|
|
||||||
|
[FR] J'ai rencontré {nb_users} personnes formidables ! Ils ont aimé mes visites {nb_likes} fois ! Je vais fêter ça en dinant !
|
||||||
|
|
||||||
|
[EN] I've met {nb_users} amazing people! And they liked me {nb_likes} times! I'll eat dinner to celebrate!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
'media': ['MICROWAVE'],
|
||||||
|
},
|
||||||
|
'DJBIRB': {
|
||||||
|
'text': ''':caique: Pwiii~ pwiii~ pwiii~ pwiii~ :caique:
|
||||||
|
|
||||||
|
[FR] Pwiiiii~ j'ai rencontré {nb_users} personnes qui m'ont aimé {nb_likes} fois, je m'amuse comme un petit fou !
|
||||||
|
|
||||||
|
[EN] Pwiiiii~ I've met {nb_users} who liked me {nb_likes} times, I'm having a lot of fun!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
'media': ['DJBIRB'],
|
||||||
|
},
|
||||||
|
'RIDING': {
|
||||||
|
'text': ''':caique: Pwii pwii pwii~ :caique:
|
||||||
|
|
||||||
|
[FR] Il n'y a pas que mon cousin qui peut s'amuser comme ça ! Regardez-moi ! ~
|
||||||
|
J'ai rencontré {nb_users} personnes et ils m'ont apprécié {nb_likes} fois !
|
||||||
|
|
||||||
|
[EN] My cousin is not the only one that can have fun! Look at me! ~
|
||||||
|
I've met {nb_users} people so far and they liked me {nb_likes} times!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
'media': ['RIDING'],
|
||||||
|
},
|
||||||
|
'RIVERDANCE': {
|
||||||
|
'text': ''':caique: Pwii 🎶 :caique:
|
||||||
|
|
||||||
|
[FR] Regardez mon cousin danser !
|
||||||
|
J'ai rencontré {nb_users} personnes et ils m'ont apprécié {nb_likes} fois !
|
||||||
|
|
||||||
|
[EN] Watch my cousin dance!
|
||||||
|
I've met {nb_users} people so far and they liked me {nb_likes} times!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
'media': ['RIVERDANCE'],
|
||||||
|
},
|
||||||
|
'BIRBVSPHONE': {
|
||||||
|
'text': ''':caique: Pwii! :caique:
|
||||||
|
|
||||||
|
[FR] Ne plaisante pas avec mon cousin, ok ? Ou sinon, gare à ton téléphone !
|
||||||
|
|
||||||
|
[EN] Don't mess with my cousin ok? He'll wreak your phone! Or at least its cover!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
'media': ['BIRDVSPHONE'],
|
||||||
|
},
|
||||||
|
'KILLERBIRD': {
|
||||||
|
'text': ''':caique: … :caique:
|
||||||
|
|
||||||
|
[FR] Mon cousin me fait peur parfois… j'espère qu'il ne fera jamais rien de tel à aucune des {nb_users} personnes que j'ai rencontré !
|
||||||
|
|
||||||
|
[EN] I'm afraid of my cousin sometimes… I hope he won't do anything like this to any of the {nb_users} people I've met so far!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
'media': ['KILLERBIRD'],
|
||||||
|
},
|
||||||
|
'WITHFRIENDS': {
|
||||||
|
'text': ''':caique: Pwiii pwii pwii~ :caique:
|
||||||
|
|
||||||
|
[FR] En train de passer du temps avec mes amis ! Je leur raconte mes rencontres avec vous tous et les {nb_likes} fois où vous avez apprécié mes visites !
|
||||||
|
|
||||||
|
[EN] Hanging out with my friends, telling them about all {nb_users} of you and how you liked my visits {nb_likes} times!''',
|
||||||
|
'privacy': 'public',
|
||||||
|
'media': ['WITHFRIENDS'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
REWARDS = [
|
||||||
|
{'required_likes': 2,
|
||||||
|
'required_users': 3,
|
||||||
|
'msg_id': 'BELLY_RUB'},
|
||||||
|
{'required_likes': 5,
|
||||||
|
'required_users': 5,
|
||||||
|
'msg_id': 'JUMPING'},
|
||||||
|
{'required_likes': 10,
|
||||||
|
'required_users': 10,
|
||||||
|
'msg_id': 'DANCINGQUEEN'},
|
||||||
|
{'required_likes': 20,
|
||||||
|
'required_users': 12,
|
||||||
|
'msg_id': 'SHOPPING'},
|
||||||
|
{'required_likes': 30,
|
||||||
|
'required_users': 15,
|
||||||
|
'msg_id': 'MICROWAVE'},
|
||||||
|
{'required_likes': 40,
|
||||||
|
'required_users': 20,
|
||||||
|
'msg_id': 'DJBIRB'},
|
||||||
|
{'required_likes': 45,
|
||||||
|
'required_users': 30,
|
||||||
|
'msg_id': 'PAPERTOWEL'},
|
||||||
|
{'required_likes': 46,
|
||||||
|
'required_users': 35,
|
||||||
|
'msg_id': 'RIDING'},
|
||||||
|
{'required_likes': 50,
|
||||||
|
'required_users': 40,
|
||||||
|
'msg_id': 'RIVERDANCE'},
|
||||||
|
{'required_likes': 60,
|
||||||
|
'required_users': 50,
|
||||||
|
'msg_id': 'BIRBVSPHONE'},
|
||||||
|
{'required_likes': 70,
|
||||||
|
'required_users': 60,
|
||||||
|
'msg_id': 'KILLERBIRD'},
|
||||||
|
{'required_likes': 80,
|
||||||
|
'required_users': 70,
|
||||||
|
'msg_id': 'WITHFRIENDS'},
|
||||||
|
]
|
485
delibird-master/main.py
Executable file
485
delibird-master/main.py
Executable file
|
@ -0,0 +1,485 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import datetime
|
||||||
|
import random
|
||||||
|
import json
|
||||||
|
import itertools
|
||||||
|
from getpass import getpass
|
||||||
|
from mastodon import Mastodon, StreamListener
|
||||||
|
from mastodon.Mastodon import MastodonAPIError, MastodonNotFoundError
|
||||||
|
from data import MEDIA, MSGS, REWARDS
|
||||||
|
|
||||||
|
API_BASE = 'https://social.sitedethib.com'
|
||||||
|
|
||||||
|
COMMAND_RE = re.compile(r'(va[s]? voir|vole vers|va, vole vers|rends visite à|go see|go visit|fly to)\s*(.+)', re.IGNORECASE)
|
||||||
|
FREE_RE = re.compile(r"(va[s]? (te promener|jouer))|rends-toi disponible|repose-toi|va-t'en|go idle|go away|((go|play) somewhere else)|take a break", re.IGNORECASE)
|
||||||
|
CANCEL_RE = re.compile(r'reviens|arrête|annule|stop|come back|cancel', re.IGNORECASE)
|
||||||
|
MENTION_RE = re.compile(r'([a-z0-9_]+)(@[a-z0-9\.\-]+[a-z0-9]+)?', re.IGNORECASE)
|
||||||
|
NOTIFY_RE = re.compile(r'dis[ -]moi|notifie[ -]moi|rappelle[ -]moi|tell me|remind me|notify me', re.IGNORECASE)
|
||||||
|
LINK_RE = re.compile(r'<a href="([^"]+)"')
|
||||||
|
|
||||||
|
STATE_OWNED, STATE_IDLE, STATE_DELIVERY = range(3)
|
||||||
|
MAX_OWNED = datetime.timedelta(hours=3)
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
"""Base error class for Delibird-generated errors"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class InternalError(Error):
|
||||||
|
"""Generic internal server error"""
|
||||||
|
def __init__(self, acct):
|
||||||
|
Error.__init__(self)
|
||||||
|
self.acct = acct
|
||||||
|
|
||||||
|
class InvalidFormatError(Error):
|
||||||
|
"""Error thrown when a command does not contain a properly-formatted
|
||||||
|
account"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class AccountNotFoundError(Error):
|
||||||
|
"""Error thrown when the requested account cannot be resolved"""
|
||||||
|
def __init__(self, acct):
|
||||||
|
Error.__init__(self)
|
||||||
|
self.acct = acct
|
||||||
|
|
||||||
|
|
||||||
|
class Delibird(StreamListener):
|
||||||
|
"""Main class for the Delibird bot."""
|
||||||
|
|
||||||
|
def __init__(self, mastodon):
|
||||||
|
StreamListener.__init__(self)
|
||||||
|
self.mastodon = mastodon
|
||||||
|
self.state = STATE_IDLE
|
||||||
|
self.owner = None
|
||||||
|
self.target = None
|
||||||
|
self.last_owned = datetime.datetime.now()
|
||||||
|
self.like_count = 0
|
||||||
|
self.visited_users = set()
|
||||||
|
self.to_be_notified = set()
|
||||||
|
self.reward_level = -1
|
||||||
|
self.last_idle_toot = None
|
||||||
|
self.additional_idle_toots = []
|
||||||
|
self.last_read_notification = None
|
||||||
|
self.own_acct_id = None
|
||||||
|
self.last_request_id = None
|
||||||
|
self.visit_to_request_map = {}
|
||||||
|
print('Delibird started!')
|
||||||
|
self.load()
|
||||||
|
self.resume()
|
||||||
|
|
||||||
|
|
||||||
|
def resume(self):
|
||||||
|
"""Process missed notifications."""
|
||||||
|
# Process all missed notifications
|
||||||
|
last_notification = self.last_read_notification
|
||||||
|
if last_notification is not None:
|
||||||
|
for notification in reversed(self.mastodon.notifications(since_id=last_notification)):
|
||||||
|
self.on_notification(notification)
|
||||||
|
|
||||||
|
|
||||||
|
def save(self, path='state.json'):
|
||||||
|
"""Save state to a JSON file."""
|
||||||
|
state = {'like_count': self.like_count,
|
||||||
|
'visited_users': list(self.visited_users),
|
||||||
|
'to_be_notified': list(self.to_be_notified),
|
||||||
|
'reward_level': self.reward_level,
|
||||||
|
'state': self.state,
|
||||||
|
'last_owned': self.last_owned.isoformat(),
|
||||||
|
'additional_idle_toots': self.additional_idle_toots,
|
||||||
|
'visit_to_request_map': self.visit_to_request_map}
|
||||||
|
if self.last_request_id is not None:
|
||||||
|
state['last_request_id'] = self.last_request_id
|
||||||
|
if self.last_read_notification is not None:
|
||||||
|
state['last_read_notification'] = self.last_read_notification
|
||||||
|
if self.last_idle_toot is not None:
|
||||||
|
state['last_idle_toot'] = self.last_idle_toot.id
|
||||||
|
if self.owner is not None:
|
||||||
|
state['owner'] = self.owner.id
|
||||||
|
if self.target is not None:
|
||||||
|
state['target'] = self.target.id
|
||||||
|
if self.own_acct_id is not None:
|
||||||
|
state['own_acct_id'] = self.own_acct_id
|
||||||
|
with open(path, 'w') as file:
|
||||||
|
json.dump(state, file)
|
||||||
|
|
||||||
|
|
||||||
|
def load(self, path='state.json'):
|
||||||
|
"""Load state from a JSON file.
|
||||||
|
May perform API requests to retrieve status or account information."""
|
||||||
|
try:
|
||||||
|
with open(path, 'r') as file:
|
||||||
|
state = json.load(file)
|
||||||
|
self.like_count = state['like_count']
|
||||||
|
self.visited_users = set(state['visited_users'])
|
||||||
|
self.to_be_notified = set(state.get('to_be_notified', []))
|
||||||
|
self.reward_level = state['reward_level']
|
||||||
|
self.state = state.get('state', STATE_IDLE)
|
||||||
|
self.last_read_notification = state.get('last_read_notification', None)
|
||||||
|
self.own_acct_id = state.get('own_acct_id', None)
|
||||||
|
self.additional_idle_toots = state.get('additional_idle_toots', [])
|
||||||
|
self.visit_to_request_map = state.get('visit_to_request_map', {})
|
||||||
|
self.last_request_id = state.get('last_request_id', None)
|
||||||
|
last_idle_toot = state.get('last_idle_toot', None)
|
||||||
|
owner = state.get('owner', None)
|
||||||
|
target = state.get('target', None)
|
||||||
|
last_owned = state.get('last_owned', None)
|
||||||
|
if last_owned:
|
||||||
|
self.last_owned = datetime.datetime.strptime(last_owned, '%Y-%m-%dT%H:%M:%S.%f')
|
||||||
|
self.last_idle_toot = None if last_idle_toot is None else self.mastodon.status(last_idle_toot)
|
||||||
|
self.owner = None if owner is None else self.mastodon.account(owner)
|
||||||
|
self.target = None if target is None else self.mastodon.account(target)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def handle_rewards(self):
|
||||||
|
"""Check rewards conditions and post appropriate toot if conditions for a
|
||||||
|
new reward are met."""
|
||||||
|
level = -1
|
||||||
|
for i, reward in enumerate(REWARDS):
|
||||||
|
if (self.like_count >= reward['required_likes']
|
||||||
|
and len(self.visited_users) >= reward['required_users']):
|
||||||
|
level = i
|
||||||
|
if level > self.reward_level:
|
||||||
|
self.reward_level = level
|
||||||
|
self.send_toot(REWARDS[level]['msg_id'],
|
||||||
|
nb_likes=self.like_count, nb_users=len(self.visited_users))
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
def upload_media(self, name):
|
||||||
|
"""Look up a media file description and upload it."""
|
||||||
|
desc = MEDIA[name]
|
||||||
|
media = self.mastodon.media_post(desc['file'],
|
||||||
|
description='Source: %s' % desc['source'])
|
||||||
|
print('Uploaded %s!' % name)
|
||||||
|
return media
|
||||||
|
|
||||||
|
|
||||||
|
def send_toot(self, msg_id, in_reply_to_id=None, **kwargs):
|
||||||
|
"""Look up a toot's description by message id and sends it."""
|
||||||
|
print('Sending a toot… id: %s' % msg_id)
|
||||||
|
msg = MSGS[msg_id]
|
||||||
|
if 'media' in msg:
|
||||||
|
if isinstance(msg['media'], dict):
|
||||||
|
grouped_choices = ([key] * count for key, count in msg['media'].items())
|
||||||
|
choices = list(itertools.chain.from_iterable(grouped_choices))
|
||||||
|
media_desc = random.choice(choices)
|
||||||
|
else:
|
||||||
|
media_desc = msg['media']
|
||||||
|
media = [self.upload_media(name) for name in media_desc]
|
||||||
|
else:
|
||||||
|
media = None
|
||||||
|
try:
|
||||||
|
status = self.mastodon.status_post(msg['text'].format(**kwargs),
|
||||||
|
media_ids=media,
|
||||||
|
in_reply_to_id=in_reply_to_id,
|
||||||
|
visibility=msg.get('privacy', ''))
|
||||||
|
except MastodonNotFoundError:
|
||||||
|
# Original status deleted
|
||||||
|
status = self.mastodon.status_post(msg['text'].format(**kwargs),
|
||||||
|
media_ids=media,
|
||||||
|
visibility=msg.get('privacy', ''))
|
||||||
|
self.own_acct_id = status.account.id
|
||||||
|
self.save()
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_account(self, text_with_user, status):
|
||||||
|
"""Process command text to resolve an account from account name, mention
|
||||||
|
or profile URL"""
|
||||||
|
receiver_acct = None
|
||||||
|
# First, see if we have a handle we can resolve, without the leading '@'
|
||||||
|
match = MENTION_RE.match(text_with_user)
|
||||||
|
if match:
|
||||||
|
if match.group(2):
|
||||||
|
# If it's a full handle, use it
|
||||||
|
receiver_acct = match.group(0)
|
||||||
|
else:
|
||||||
|
# If it's *not* a full handle, append the user's domain
|
||||||
|
receiver_acct = '@'.join([match.group(1)] + status.account.acct.split('@')[1:])
|
||||||
|
try:
|
||||||
|
matches = self.mastodon.account_search(receiver_acct)
|
||||||
|
except MastodonAPIError:
|
||||||
|
raise InternalError(receiver_acct)
|
||||||
|
if not matches:
|
||||||
|
raise AccountNotFoundError(receiver_acct)
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
# Maybe it's a link to their profile, or a mention. Switch to link handling.
|
||||||
|
match = LINK_RE.search(text_with_user)
|
||||||
|
if match:
|
||||||
|
url = match.group(1)
|
||||||
|
try:
|
||||||
|
matches = self.mastodon.search(url, resolve=True).accounts
|
||||||
|
except MastodonAPIError:
|
||||||
|
raise InternalError(url)
|
||||||
|
if matches:
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
raise InvalidFormatError
|
||||||
|
|
||||||
|
|
||||||
|
def handle_unknown_account(self, status, acct):
|
||||||
|
"""Handle unknown accounts, potentially suggesting other accounts."""
|
||||||
|
receiver_acct = acct.split('@')
|
||||||
|
suggested_account = None
|
||||||
|
if len(receiver_acct) > 1:
|
||||||
|
username, domain = receiver_acct
|
||||||
|
try:
|
||||||
|
matches = self.mastodon.account_search(username, limit=40)
|
||||||
|
except MastodonAPIError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for match in matches:
|
||||||
|
if match.username != username:
|
||||||
|
continue
|
||||||
|
if domain in match.acct:
|
||||||
|
suggested_account = match
|
||||||
|
break
|
||||||
|
if suggested_account is None:
|
||||||
|
self.send_toot('ERROR_UNKNOWN_ACCOUNT', status,
|
||||||
|
sender_acct=status.account.acct, acct=acct)
|
||||||
|
else:
|
||||||
|
self.send_toot('ERROR_UNKNOWN_ACCOUNT2', status,
|
||||||
|
sender_acct=status.account.acct, acct=acct,
|
||||||
|
suggested_acct=suggested_account.acct)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_cmd_free(self, status, match=None):
|
||||||
|
"""Handle the command that allows the bird to go idle prematurely"""
|
||||||
|
if not self.owner or self.owner.id != status.account.id:
|
||||||
|
return
|
||||||
|
if self.state != STATE_OWNED:
|
||||||
|
return
|
||||||
|
self.go_idle('IDLE2')
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_cmd_go_see(self, status, match):
|
||||||
|
"""Handle the “go see” command requesting the bot to visit a given user"""
|
||||||
|
text_with_user = match.group(2)
|
||||||
|
if self.state == STATE_DELIVERY:
|
||||||
|
if self.owner.id == status.account.id:
|
||||||
|
self.send_toot('ERROR_DELIVERY2', status, sender_acct=status.account.acct)
|
||||||
|
elif status.account.acct in self.to_be_notified:
|
||||||
|
self.send_toot('ERROR_DELIVERY3', status, sender_acct=status.account.acct)
|
||||||
|
else:
|
||||||
|
self.send_toot('ERROR_DELIVERY', status, sender_acct=status.account.acct)
|
||||||
|
return
|
||||||
|
if self.state == STATE_OWNED and self.owner and self.owner.id != status.account.id:
|
||||||
|
delta = self.last_owned + MAX_OWNED - datetime.datetime.now()
|
||||||
|
minutes = delta.seconds // 60
|
||||||
|
if status.account.acct in self.to_be_notified:
|
||||||
|
self.send_toot('ERROR_OWNED2', status, sender_acct=status.account.acct,
|
||||||
|
hours=(minutes // 60), minutes=(minutes % 60))
|
||||||
|
else:
|
||||||
|
self.send_toot('ERROR_OWNED', status, sender_acct=status.account.acct,
|
||||||
|
hours=(minutes // 60), minutes=(minutes % 60))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
target = self.resolve_account(text_with_user, status)
|
||||||
|
except AccountNotFoundError as err:
|
||||||
|
self.handle_unknown_account(status, err.acct)
|
||||||
|
return
|
||||||
|
except InvalidFormatError:
|
||||||
|
self.send_toot('ERROR_INVALID_FORMAT', status,
|
||||||
|
sender_acct=status.account.acct)
|
||||||
|
return
|
||||||
|
except InternalError as err:
|
||||||
|
self.send_toot('ERROR_INTERNAL', status,
|
||||||
|
sender_acct=status.account.acct, acct=err.acct)
|
||||||
|
return
|
||||||
|
|
||||||
|
if target.id == self.own_acct_id:
|
||||||
|
self.send_toot('ERROR_OWN_ACCOUNT', status,
|
||||||
|
sender_acct=status.account.acct)
|
||||||
|
return
|
||||||
|
|
||||||
|
if target.id == status.account.id:
|
||||||
|
self.send_toot('ERROR_SAME_ACCOUNT', status,
|
||||||
|
sender_acct=status.account.acct)
|
||||||
|
return
|
||||||
|
|
||||||
|
if '#nobot' in target.note:
|
||||||
|
self.send_toot('ERROR_NOBOT', status,
|
||||||
|
sender_acct=status.account.acct,
|
||||||
|
receiver_acct=target.acct)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.state = STATE_DELIVERY
|
||||||
|
self.last_request_id = status.id
|
||||||
|
self.owner = status.account
|
||||||
|
self.last_owned = datetime.datetime.now()
|
||||||
|
self.target = target
|
||||||
|
|
||||||
|
if self.last_idle_toot is not None:
|
||||||
|
try:
|
||||||
|
self.mastodon.status_delete(self.last_idle_toot)
|
||||||
|
except MastodonAPIError:
|
||||||
|
pass
|
||||||
|
self.last_idle_toot = None
|
||||||
|
for toot_id in self.additional_idle_toots:
|
||||||
|
try:
|
||||||
|
self.mastodon.status_delete(toot_id)
|
||||||
|
except MastodonAPIError:
|
||||||
|
pass
|
||||||
|
self.additional_idle_toots = []
|
||||||
|
|
||||||
|
self.send_toot('DELIVERY_START', status, sender_acct=status.account.acct, acct=self.target.acct)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_cmd_cancel(self, status, match=None):
|
||||||
|
"""Handle the “cancel” command that cancels the last ordered delivery if the
|
||||||
|
user issuing it is the current owner and the delivery hasn't finished yet."""
|
||||||
|
if not self.owner or self.owner.id != status.account.id:
|
||||||
|
return
|
||||||
|
if self.state != STATE_DELIVERY:
|
||||||
|
return
|
||||||
|
self.state = STATE_OWNED
|
||||||
|
self.send_toot('DELIVERY_CANCELLED', status, sender_acct=status.account.acct,
|
||||||
|
acct=self.target.acct)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_cmd_notify(self, status, match=None):
|
||||||
|
"""Handle request to be notified when the Birb goes idle."""
|
||||||
|
if self.state == STATE_IDLE:
|
||||||
|
return
|
||||||
|
self.to_be_notified.add(status.account.acct)
|
||||||
|
self.send_toot('NOTIFY_REQUEST', status, acct=status.account.acct)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_mention(self, status):
|
||||||
|
"""Handle toots mentioning Delibird, which may contain commands"""
|
||||||
|
print('Got a mention!')
|
||||||
|
# Do not reply if multiple people are mentionned
|
||||||
|
if len(status.mentions) > 2:
|
||||||
|
return
|
||||||
|
# Process commands, in order of priority
|
||||||
|
cmds = [(COMMAND_RE, self.handle_cmd_go_see),
|
||||||
|
(CANCEL_RE, self.handle_cmd_cancel),
|
||||||
|
(FREE_RE, self.handle_cmd_free),
|
||||||
|
(NOTIFY_RE, self.handle_cmd_notify)]
|
||||||
|
for regexp, handler in cmds:
|
||||||
|
match = regexp.search(status.content)
|
||||||
|
if match:
|
||||||
|
handler(status, match)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def deliver(self):
|
||||||
|
"""Deliver a message to the target, updating ownership and state in the
|
||||||
|
process"""
|
||||||
|
try:
|
||||||
|
status = self.send_toot('DELIVERED',
|
||||||
|
sender_acct=self.owner.acct,
|
||||||
|
sender_acct_short=self.owner.acct,
|
||||||
|
receiver_acct=self.target.acct,
|
||||||
|
nb_hours=(MAX_OWNED.seconds // 3600))
|
||||||
|
except MastodonAPIError:
|
||||||
|
try:
|
||||||
|
status = self.send_toot('DELIVERED',
|
||||||
|
sender_acct=self.owner.acct,
|
||||||
|
sender_acct_short=self.owner.username,
|
||||||
|
receiver_acct=self.target.acct,
|
||||||
|
nb_hours=(MAX_OWNED.seconds // 3600))
|
||||||
|
except MastodonAPIError:
|
||||||
|
status = self.send_toot('DELIVERED',
|
||||||
|
sender_acct=self.owner.username,
|
||||||
|
sender_acct_short=self.owner.username,
|
||||||
|
receiver_acct=self.target.acct,
|
||||||
|
nb_hours=(MAX_OWNED.seconds // 3600))
|
||||||
|
if status.mentions:
|
||||||
|
self.visit_to_request_map[status.id] = self.last_request_id
|
||||||
|
self.owner = self.target
|
||||||
|
self.last_owned = datetime.datetime.now()
|
||||||
|
self.state = STATE_OWNED
|
||||||
|
self.visited_users.add(self.owner.id)
|
||||||
|
self.to_be_notified.discard(self.owner.acct)
|
||||||
|
self.save()
|
||||||
|
else:
|
||||||
|
# Could not deliver, maybe OStatus-only?
|
||||||
|
self.state = STATE_OWNED
|
||||||
|
self.send_toot('ERROR_UNDELIVERABLE',
|
||||||
|
sender_acct=self.owner.acct,
|
||||||
|
receiver_acct=self.target.acct)
|
||||||
|
|
||||||
|
|
||||||
|
def go_idle(self, msg_id='IDLE'):
|
||||||
|
"""Turn idle and announce it with a public toot"""
|
||||||
|
self.state = STATE_IDLE
|
||||||
|
self.last_idle_toot = self.send_toot(msg_id)
|
||||||
|
self.save()
|
||||||
|
self.additional_idle_toots = [self.send_toot('NOTIFY_IDLE', self.last_idle_toot,
|
||||||
|
acct=acct).id
|
||||||
|
for acct in self.to_be_notified]
|
||||||
|
self.to_be_notified = set()
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_heartbeat(self):
|
||||||
|
if self.state == STATE_DELIVERY and self.target is not None and random.random() >= 0.94:
|
||||||
|
self.deliver()
|
||||||
|
elif self.state == STATE_DELIVERY:
|
||||||
|
self.handle_rewards()
|
||||||
|
elif self.state == STATE_OWNED and datetime.datetime.now() - self.last_owned > MAX_OWNED:
|
||||||
|
self.go_idle()
|
||||||
|
|
||||||
|
|
||||||
|
def on_notification(self, notification):
|
||||||
|
self.last_read_notification = notification.id
|
||||||
|
if notification.type == 'mention':
|
||||||
|
self.handle_mention(notification.status)
|
||||||
|
if notification.type == 'favourite' and notification.status.visibility == 'direct':
|
||||||
|
self.like_count += 1
|
||||||
|
if notification.status.id in self.visit_to_request_map:
|
||||||
|
try:
|
||||||
|
self.mastodon.status_favourite(self.visit_to_request_map[notification.status.id])
|
||||||
|
except MastodonNotFoundError:
|
||||||
|
del self.visit_to_request_map[notification.status.id]
|
||||||
|
except MastodonAPIError:
|
||||||
|
pass
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def register(args):
|
||||||
|
"""Register app on the server"""
|
||||||
|
Mastodon.create_app('Delibird', api_base_url=args.api_base, to_file='secrets/clientcred.secret')
|
||||||
|
|
||||||
|
|
||||||
|
def login(args):
|
||||||
|
"""Log in as the given user, generating OAuth credentials"""
|
||||||
|
mastodon = Mastodon(client_id='secrets/clientcred.secret', api_base_url=args.api_base)
|
||||||
|
mastodon.log_in(args.user_mail, getpass(), to_file='secrets/usercred.secret')
|
||||||
|
|
||||||
|
|
||||||
|
def run(args):
|
||||||
|
"""Run the bot"""
|
||||||
|
mastodon = Mastodon(client_id='secrets/clientcred.secret',
|
||||||
|
access_token='secrets/usercred.secret',
|
||||||
|
api_base_url=args.api_base)
|
||||||
|
delibird = Delibird(mastodon)
|
||||||
|
print('Starting streaming!')
|
||||||
|
mastodon.stream_user(delibird)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('command', type=str, choices=['register', 'login', 'run'])
|
||||||
|
parser.add_argument('-a', '--api-base', type=str, default=API_BASE)
|
||||||
|
parser.add_argument('-u', '--user-mail', type=str)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == 'register':
|
||||||
|
register(args)
|
||||||
|
elif args.command == 'login':
|
||||||
|
login(args)
|
||||||
|
elif args.command == 'run':
|
||||||
|
run(args)
|
||||||
|
|
||||||
|
main()
|
Loading…
Reference in New Issue
Block a user