Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
DenchyRS authored Jun 4, 2023
1 parent c23720a commit 46e5778
Show file tree
Hide file tree
Showing 4 changed files with 332 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .env template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# .env
DISCORD_TOKEN=
BB_NAME=
BB_PASS=
105 changes: 105 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@

# Birdini BirdBuddy Discord Companion

Birdini is a Discord bot that utilises post notifications, images, and videos from your BirdBuddy into a Discord channel, right as the magic happens. Share all those wonderful bird sitings with your community and friends automatically!


![alttext](https://imgur.com/AbA50Py.png)





## Commands

| Command | Arguments | Description |
| :-------- | :------- | :------------------------- |
| `/channel` | `channel` or `channelID` | Set which channel you want Birdini to post noitifications to. |
|`/muted_channel`| `channel` or `channelID` | Set which channel you want Birdini to post notifications to when muted (private channel recommended).|
|`/mute` | `None`| Birdini will no longer post messages in the primary channel and send them to the defined muted channel instead.
|`/unmute`|`None`| Birdini will continue to post messages in the primary channel defined.


## Environment Variables

To run this project, you will need to add the following environment variables to your .env file or use the template provided in the Git.

`DISCORD_TOKEN`
Discord bot token that can be gotten from https://discord.com/developers.

`BB_NAME`
This is the email used to log into your BirdBuddy account. Note that this has to be an email/password login and can not be an integration like login with Google. A good work around for this issue (if your primary account is using such type of login). Is to create a new account using an email and then inviting them to be a guest at your feeder.

`BB_PASS`
This is the password used to log into your BirdBuddy account.

# Setup Guide
This makes use of the [pybirdbuddy](https://github.com/jhansche/pybirdbuddy/releases/tag/v0.0.14) library for API calls.

### Files
How to aquire and arrange all the files needed in order to get Birdini to work.
- [Download](https://github.com/jhansche/pybirdbuddy/releases/tag/v0.0.14) the pybirdbuddy library. Birdini was built using v0.0.14. Newer versions may work but v0.0.14 is recommended.
- Create a new folder, I will be calling mine Birdini Bot for the sake of this guide.
- Place the pybirdbuddy-0.0.14 folder inside your new one and rename it `pybirdbuddy`.
- Download the files from this Git and place them in your Birdini Bot folder.
- You should then end up with a folder looking like this: ![alttext](https://i.imgur.com/jbLGL7q.png)
- Insert the appropriate information into your .env file. Do not worry yet about the `DISCORD_TOKEN` as we will be getting this next.


### Discord Bot

A brief guide on how to create a Discord bot and obtain your `DISCORD_TOKEN`.
- Go to https://discord.com/developers/applications
- Click "New Application" in the top right and name your bot.
- Go to the "Bot" section, disable "PUBLIC BOT" and enable all other settings.
- Press "Reset Token" towards the top of the page and copy your new token.
- Add the copied token to your .env file. It should look like this `DISCORD_TOKEN=1234567890`
- Go to the "OAuth2" section and select "URL Generator".
- Select the "bot" tickbox and then the "Administrator" tickbox that will appear in the new interface below.
- Copy the link generated at the bottom, paste it into your browser, and add the bot to your server.

### Setting up AWS (free)
How to setup and host your bot 24/7, for free using AWS!
- Go to https://aws.amazon.com/
- Press the "Sign in to the console" button in the top right.
- You will be redirected to a login screen. Log in or create your account.
- On the "Console Home" page you can scroll down and see a "Build a solution" header.
- Under this header you will see "Launch a virtual machine (With EC2)", select this.
- Ensure that the Application and OS Images are set to "Amazon Linux" (it should be this by default but check anyway).
- Scroll down further until you see "Key pair (login)". Select "Create a new key pair".
- Name this whatever you like and leave it on the default settings.
- When this is all done press the "Launch instance" button on the right side.
- At the very top left of the screen you can click the word "Instances" to be taken to your list of instances.
- It may take some time to setup, just wait until Instance state says "Running" and the status checks have passed.

Now the instance is created we are going to need to upload our files and install some Python dependencies. We will need some additional software to easily upload our files to the AWS. I will be using FileZilla for this guide which can be found [here](https://filezilla-project.org/).

- We are going to need to connect to our server using FileZilla. To see the information we are going to need we should click the string of bluee text under Instance ID.
- Now open FileZilla and select Edit > Settings
- Locate the "SFTP" section and select "Add key file". This is what we download earlier when we created a new key pair.
- Once this has been selected we can exit out of the settings menu and navigate to File > Site Manager.
- Press "New site". Change the Protocol to SFTP.
- For the Host we are going to want to enter public IPv4 address that can be seen on our instance page. E.g 3.95.161.48
- Under User enter `ec2-user` and press connect.
- On the left hand side we are going to locate our Birdini Bot folder.
- You should now see something like this. ![alttext](https://i.imgur.com/M2CZuJI.png)
- Highlight and drag over all the files into the ec2-user folder.
- Now we have all our files on the server we just need to install some dependencies via the console and we can get the bot running!
- Head back to your AWS instance web page and selec the "Connect" button in the top right, press connect again. This will take you to the AWS console.
- Type `sudo yum install python3-pip`. You will then be asked if this is ok, type `y` and press Enter.
- We can now install the other dependencies using the following commands.
- pip install discord
- pip install python-dotenv
- pip install langcodes
- pip install python_graphql_client
- Now that all our dependencies are finally installed we are ready to start the bot! We will be using the screen command to do this.
- Type `screen -d -m python3 bot.py`.
- The bot should now be running, appear online in discord, and be ready to send notifications!
- If you want to shutdown the bot for whatever reason you can type `screen -ls` to see a list of detatched instances you have running (this should just be one).
- Now to shut this down you want type `screen -X -S [session you want to kill] quit`.
- The session name shown using `screen -ls` will always be different so bare that in mind.
- Here's what my command looked like `screen -X -S 3945..ip-172-31-93-222 quit`
- `3945..ip-172-31-93-222` was the session name that AWS generated for me.



218 changes: 218 additions & 0 deletions bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
# Birdini bot.py
import os
import discord
import json
import datetime
import requests

from dotenv import load_dotenv
from pybirdbuddy.birdbuddy.client import BirdBuddy
from discord import app_commands
from discord.ext import commands, tasks
from discord.ext.commands import has_permissions
from datetime import datetime

# Load enviroment variables
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
EMAIL = os.getenv('BB_NAME')
PASS = os.getenv('BB_PASS')

# Open json file
with open('discVars.json') as discVars:
jsonData = json.load(discVars)

# Login to birdbuddy feeder
bb = BirdBuddy(EMAIL, PASS)
bb.language_code = "en"

intents = discord.Intents.all()

# This is the URL used to insert multiple images into embeds. This can be changed but it's greatly appreciated if you leave it :)
embedUrl = "https://ko-fi.com/denchy"

bot = commands.Bot(command_prefix='!', intents=intents)


@bot.event
async def on_ready():
print(f'{bot.user.name} has connected to Discord!')
bird_sighting_new.start()
try:
synced = await bot.tree.sync()
print(f"Synced {len(synced)} command(s)")
except Exception as e:
print(e)

# Create buttons to attatch to embed


class EmbedButtons(discord.ui.View):
def __init__(self):
super().__init__(timeout=None)
# Always add support me button
supportMeButton = discord.ui.Button(
label="Support Me!", style=discord.ButtonStyle.blurple, url=embedUrl, emoji="❤️")
self.add_item(supportMeButton)

# Change what channel the public messages will be sent too


@bot.tree.command(name="channel", description="Define what channel you want to send public notifications to.")
@has_permissions(administrator=True)
@app_commands.describe(channel="Select a channel you wish to push notifications to. You can also enter the channel ID.")
async def set_message_channel(interaction: discord.Interaction, channel: discord.TextChannel):
print(f"Message channel updated to {channel}")
jsonData['ChannelID'] = int(channel.id)
with open("discVars.json", "w") as fileA:
json.dump(jsonData, fileA)
await interaction.response.send_message(f"{bot.user.name} will now post notifications to {channel}", ephemeral=True)

# Change what channel the 'muted' messages will be sent too


@bot.tree.command(name="muted_channel", description="Define what channel you want to redirect public notifications to when muted.")
@has_permissions(administrator=True)
@app_commands.describe(channel="Select a channel you wish to redirect notifications to when muted. You can also enter the channel ID.")
async def set_message_channel_muted(interaction: discord.Interaction, channel: discord.TextChannel):
print(f"Muted message channel updated to {channel}")
jsonData['mutedChannelID'] = int(channel.id)
with open("discVars.json", "w") as fileA:
json.dump(jsonData, fileA)
await interaction.response.send_message(f"{bot.user.name} will now post notifications to {channel} when muted", ephemeral=True)


@bot.tree.command(name='mute', description="Mute the bot, the bot will continue to post notifications to your desired privated channel.")
@has_permissions(administrator=True)
async def mute_bot(interaction: discord.Interaction):
if jsonData['isMuted']:
await interaction.response.send_message(f"{bot.user.name} is already muted!", ephemeral=True)
return
jsonData['isMuted'] = True
with open("discVars.json", "w") as fileA:
json.dump(jsonData, fileA)
await interaction.response.send_message(f"{bot.user.name} is now muted and will no longer post to the public channel!", ephemeral=True)


@bot.tree.command(name='unmute', description="Unmute the bot, let everyone see those beautiful birds again!")
@has_permissions(administrator=True)
async def unmute_bot(interaction: discord.Interaction):
if not jsonData['isMuted']:
await interaction.response.send_message(f"{bot.user.name} isn't muted!", ephemeral=True)
return
jsonData['isMuted'] = False
with open("discVars.json", "w") as fileA:
json.dump(jsonData, fileA)
await interaction.response.send_message(f"{bot.user.name} is now unmuted and will continue to post to the public channel!", ephemeral=True)


# timer stuff
@tasks.loop(seconds=60.0)
async def bird_sighting_new():
channelID = jsonData['ChannelID']
mChannelID = jsonData['mutedChannelID']
isMuted = jsonData['isMuted']
# channel id you want the bird auto-notifications to be sent to
channel = bot.get_channel(channelID)
mChannel = bot.get_channel(mChannelID)
if channel is None:
print("A valid channel for public alerts was not selected.")
return
elif mChannel is None and isMuted is True:
print("A valid channel for muted alerts was not selected.")
return
# BB CODE
getPostcards = await bb.new_postcards()

# return if no new postcards are detected
if len(getPostcards) == 0:
print("No new postcards found.")
return

# after postcard sighting is confirmed use finishPostcard
getSightings = await bb.sighting_from_postcard(getPostcards[0])
getReport = getSightings.report
# get report status and set type

imageUrls = [item['contentUrl'] for item in getSightings.medias]
videoUrls = [item['contentUrl'] for item in getSightings.video_media]

# Determine if there is a video url and select appropriate emoji for embed
if len(videoUrls) > 0:
videoEmoji = 'Yes'
hasVideo = True
else:
videoEmoji = 'No'
hasVideo = False

appendToDescription = f"\n🖼️ Images captured: {len(imageUrls)} \n📹 Video captured: {videoEmoji}"

split_string = str(getReport).split("'")
recognized_phrase = split_string[3]
if recognized_phrase == "mystery" or recognized_phrase == "best_guess":
birdIcon = ""
descriptionText = f"🐦 Total visits: ??{appendToDescription}"
embedTitle = "Unidentifiable bird spotted!"
embedColor = 0xb5b5b6
else:

birdName = getReport['sightings'][0]['species']['name']
birdIcon = getReport['sightings'][0]['species']['iconUrl']
try:
birdVisitCount = getReport['sightings'][0]['count']
descriptionText = f"🐦 Total visits: {str(birdVisitCount)}{appendToDescription}"
embedTitle = f"{birdName} spotted!"
embedColor = 0x4dff4d
except:
birdVisitCount = 1
descriptionText = f"This is your first time being visited by a {birdName}!\n\n🐦 Total Visits: 1{appendToDescription}"
embedTitle = f"{birdName} unlocked!"
embedColor = 0xf1c232

# Weird embed merge bug thing idk, allows for multiple images in a single embed. Mobile will only show one however.
embed = discord.Embed(title=embedTitle,
url=embedUrl,
description=descriptionText,
color=embedColor,
timestamp=datetime.now())
# embed.set_image(url=imageUrls[0])
image_data = requests.get(imageUrls[0]).content
with open('image.png', 'wb') as handler:
handler.write(image_data)
# ^old method linked expires, new one below for test... download and upload the files to discords server and then embed them
file = discord.File("image.png", filename="image.png")
files = [file]

embed = discord.Embed(title=embedTitle,
url=embedUrl,
description=descriptionText,
color=embedColor,
timestamp=datetime.now())
embed.set_image(url="attachment://image.png")
embed.set_footer(text="Birdini by Denchy", icon_url=bot.user.avatar)

embeds = [embed]

# Process additional images
if len(imageUrls) > 1:
for i in range(1, min(len(imageUrls), 4)):
image_data = requests.get(imageUrls[i]).content
with open(f'image{i}.png', 'wb') as handler:
handler.write(image_data)
file = discord.File(f"image{i}.png", filename=f"image{i}.png")
files.append(file)

embed = discord.Embed(url=embedUrl)
embed.set_image(url=f"attachment://image{i}.png")
embeds.append(embed)

# Send embed with attachments to appropriate channel
if isMuted:
await mChannel.send(files=files, embeds=embeds, view=EmbedButtons())
else:
await channel.send(files=files, embeds=embeds, view=EmbedButtons())

# uncomment this to finish postcards and send them to collections!
# await bb.finish_postcard(getPostcards[0]['id'], getSightings)

bot.run(TOKEN)
5 changes: 5 additions & 0 deletions discVars.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"ChannelID": 0,
"isMuted": false,
"mutedChannelID": 0
}

0 comments on commit 46e5778

Please sign in to comment.