Giter Club home page Giter Club logo

sandpiper's Introduction

Sandpiper

release license standard-readme compliant

Sandpiper is a Discord bot that makes it easier to communicate with friends around the world.

Her current features include:

  • Unit conversion between imperial and metric quantities
  • Time conversion between the timezones of users in your server
  • Miniature user bios
    • Set your preferred name, pronouns, birthday, and timezone
    • Manage the privacy of each of these fields (private by default, but they may be set to public visibility)
  • Search for users by their preferred name, Discord username, or server nicknames

Table of Contents

Install

Prerequisites

  • Poetry – dependency manager
  • (Optional) pyenv – Python version manager
  • (Optional) PM2 – process manager

Install Sandpiper

To get started, clone the repo.

git clone https://github.com/phanabani/sandpiper.git
cd sandpiper

Install the dependencies with Poetry. Sandpiper requires Python 3.10.

# If you're using pyenv, run the following to init a Poetry environment using
# the correct Python version
poetry env use $(pyenv which python)

# Install dependencies
poetry install --no-root --no-dev

Usage

Set up configuration

Create a json file sandpiper/config.json (or copy sandpiper/config_example.json). The only value you need to set is the bot_token.

{
    "bot_token": "<YOUR_BOT_TOKEN>"
}

See config for more info.

Running Sandpiper

Basic

In the top level directory, simply run Sandpiper as a Python module with Poetry.

poetry run python -m sandpiper

With PM2

PM2 is a daemon process manager. Ensure you've followed the virtual environment setup described above, then simply run the following command in Sandpiper's root directory:

pm2 start

This starts the process as a daemon using info from ecosystem.config.js.

Inviting Sandpiper to your Discord server

Sandpiper requires the following permissions to run normally:

  • View Channels
  • Send Messages
  • Embed links

These correspond to the permission integer 19456.

Sandpiper also requires the following privileged intents:

  • Server members
    • For Discord username/server nickname lookup in the whois command
  • Message content
    • For running commands (will be replaced by slash commands)
    • For searching messages for unit/time conversion strings

You can enable these on the bot page of your application (https://discord.com/developers/applications/CLIENT_ID/bot).

Config

Sandpiper can be configured with a JSON file at sandpiper/config.json. sandpiper/config_example.json contains default values and can be used as a template. bot_token is the only required field.

See Config for detailed information about setting up the config file.

Commands and features

In servers, commands must be prefixed with the configured command prefix (default="!piper "). When DMing Sandpiper, you do not need to prefix commands.

Unit conversion

Convert measurements written in regular messages! Just surround a measurement in curly brackets -- like this: {5 ft} -- and Sandpiper will convert it for you. You can put multiple measurements in a message as long as each is put in its own brackets.

Many measurements are converted by default without needing to specify an output unit. Read Default unit mappings to see all currently supported default conversions.

You can explicitly specify an output unit like this: {2 tonnes > lbs}. This opens up to you nearly any unit conversion you may need.

Lastly, you can do math in conversions, too! {2.3 ft + 5 in}

Examples

guys it's {30f} outside today, I'm so cold...

I've been working out a lot lately and I've already lost {2 kg}!!

I think Jason is like {6' 2"} tall

Lou lives about {15km} from me and TJ's staying at a hotel {1.5km} away, so he and I are gonna meet up and drive over to Lou.

I weigh about 9.3 stone. For you americans, that's {9.3 stone > lbs}

my two favorite songs are {5min+27s} and {4min+34s}. that's a total time of {5min+27s + 4min+34s > s} seconds

hey sandpiper what's {30 * 7}?

Time conversion

Just like unit conversion, you can also convert times between timezones! Surround a time in curly brackets {5:30pm} and Sandpiper will convert it to the different timezones of users in your server.

Times can be formatted in 12- or 24-hour time and use colon separators (HH:MM). 12-hour times can optionally include AM or PM to specify what half of the day you mean. If you don't specify, AM will be assumed.

You can use the keywords now, midnight, and noon instead of a numeric time.

You can put multiple times in a message as long as each is put in its own brackets.

You can explicitly specify input and output timezones very similarly to how units are specified in unit conversion:

Timezone specification How to write What it does
Input timezone {5:45 PM London} Converts 5:45 PM in London time to all timezones of users in your server
Output timezone {5:45 PM > Los Angeles} Converts 5:45 PM from your timezone to Los Angeles time
Input & output timezone {5:45 PM Amsterdam > Helsinki} Converts 5:45 PM in Amsterdam time to Helsinki time

To use this feature without having to specify input/output timezones every time, you and your friends need to set your timezones with the timezone set command (see the bio commands section for more info).

Examples

do you guys wanna play at {9pm}?

I wish I could, but I'm busy from {14} to {17:45}

yeah I've gotta wake up at {5} for work tomorrow, so it's an early bedtime for me

ugh I have a 2 hr meeting at {noon} tomorrow

my flight took off at {7pm new york} and landed at {8 AM london}

what time is it in dubai right now? {now > dubai}

the game's releasing at {1 PM > new york} for americans and {1500 > london} for europeans

hey alex, jaakko's getting on at {8 pm helsinki > amsterdam}

Bios

Store some info about yourself to help your friends get to know you more easily! Unless specified otherwise in the config file, most of these commands can only be used in DMs with Sandpiper for your privacy.

General commands

Command Description
bio show Display all your stored info and their privacy settings.
bio delete Delete all your stored info.

Setting your info

Setting a field doesn't automatically make it public. See the privacy section for more info about managing your privacy.

Command Description Example
name set <new_name> Set your preferred name (max 64 characters). name set Phana
pronouns set <new_pronouns> Set your pronouns (max 64 characters). pronouns set She/Her
birthday set <new_birthday> Set your birthday in one of the following formats: 1997-08-27, 8 August 1997, Aug 8 1997, August 8, 8 Aug. You may omit your birth year with the month-name format (age will not be calculated). birthday set 1997-08-27
timezone set <new_timezone> Set your timezone. Don't worry about formatting. Typing the name of the nearest major city should be good enough, but you can also try your state/country if that doesn't work. If you're confused, use this website to find your full timezone name: http://kevalbhatt.github.io/timezone-picker timezone set new york

Displaying your info

Command Description Example
name show Display your preferred name. name show
pronouns show Display your pronouns. pronouns show
birthday show Display your birthday. birthday show
age show Display your age (calculated automatically from your birthday). age show
timezone show Display your timezone. timezone show

Deleting your info

Command Description Example
name delete Delete your preferred name. name delete
pronouns delete Delete your pronouns. pronouns delete
birthday delete Delete your birthday. birthday delete
timezone delete Delete your timezone. timezone delete

Manage the privacy of your info

You can set the privacy for each field in your bio to either public or private. Everything is private by default. If you set a field as public, anyone may be able to see it as long as they are in the same server as you and Sandpiper.

Command Description Example
privacy all <new_privacy> Set the privacy of all your info at once. privacy all public
privacy name <new_privacy> Set the privacy of your preferred name. privacy name public
privacy pronouns <new_privacy> Set the privacy of your pronouns. privacy pronouns public
privacy birthday <new_privacy> Set the privacy of your birthday. privacy birthday private
privacy age <new_privacy> Set the privacy of your age. privacy age private
privacy timezone <new_privacy> Set the privacy of your timezone. privacy timezone public

Search for users by one of their names

If you're new to a server, you might hear someone's name floating around but not know who they are. Sandpiper lets you search for users by either their preferred name (configured with the name set command), their Discord username, or their nickname in a server you are both in.

You can run this command in a server or in Sandpiper DMs.

Command Description Example
whois <name> Search for a user by one of their names on Discord. whois jason

Birthday notifications

Sandpiper can announce your birthday to your friends!

To enable this feature, set some of your info with the bios commands:

  • Birthday
  • [Optional] Timezone
  • [Optional] Name
  • [Optional] Pronouns

You also need to set these fields to public with the privacy commands for them to be used (otherwise, they are private by default, and Sandpiper will not use them to keep your personal info private).

Here is how your personal info will be used to create your birthday announcement message:

  • Birthday
    • (Public) Your birthday will be announced on every server you and Sandpiper are in together
    • (Private) Your birthday will not be announced
  • Timezone
    • (Public) Your birthday will be announced at midnight in your timezone
    • (Private) Your birthday will be announced at midnight UTC (coordinated universal time)
  • Name
    • (Public) Your preferred name will be used in the message
    • (Private) Your Discord username will be used in the message
  • Pronouns
    • (Public) Your pronouns will be used in the message
    • (Private) They/them will be used in the message
  • Age
    • (Public and birth year set) Your new age will be displayed in the message
    • (Private or birth year not set) Your new age will not be displayed in the message

Commands

Command Description Example
birthdays upcoming Show upcoming and past birthdays. birthdays upcoming

Developers

Installation

Follow the installation steps in install and use Poetry to install the development dependencies:

poetry install --no-root

Testing

Run unit tests

poetry run python -m pytest --pyargs sandpiper
# or run tests with profiling (--profile-svg to generate svg image):
poetry run python -m pytest --pyargs --profile sandpiper

Run tests with code coverage

poetry run coverage run -m pytest --pyargs sandpiper
poetry run coverage html

Open htmlcov/index.html to view the coverage report.

Changelog

Check out Sandpiper's version history in CHANGELOG.md!

Planned features

  • Unit conversion
  • User bios
    • Preferred name
    • Pronouns
    • Birthday
    • Timezone
  • Time conversion
  • Birthday notifications
  • Thread support
  • Slash commands
  • Conversion editing/deletion
  • Time conversion images
  • Currency conversions

Inspiration

These Discord bots inspired the development of Sandpiper:

License

MIT © Phanabani.

sandpiper's People

Stargazers

 avatar  avatar

Watchers

 avatar

sandpiper's Issues

Handle users editing/deleting messages with conversions

When a user edits a conversion, Sandpiper should pick up on this and edit her reply with the new conversion. Likewise, if a user deletes their message with a conversion, she should delete her reply. The deletion could also be a config field.

Add unit tests for birthday announcements

We'll probably need to patch asyncio.sleep somewhere...
Edit: yeah we sure did lol

  • Birthday in future
  • Birthday in the past (schedule if still birthday, don't otherwise)
  • Privacies
    • Birthday (don't send at all)
    • Age (don't put age in notif)
    • Name (use server nickname)
    • Pronouns (use they/them)
    • Timezone (use UTC)
  • Changing timezone/birthday while already scheduled
  • Send in several servers
  • Don't send (or error) if no birthday channel set
  • Don't error if birthday channel no longer exists

Tell users how privacy values tie into birthday message formatting

Enable/disable sending their birthday notification
Setting to display age in notif? Maybe this could be controlled by age privacy

I'm reconsidering these two issues. I think it's most logical to have the privacy settings control what data gets accessed by the birthday alerts module. For example:

  • Private birthdays won't be announced
  • Private ages won't be put in the birthday message
  • Private/missing preferred names will be replaced by regular usernames in the birthday message

However, is it intuitive that having your age as public means it will be announced? It doesn't seem so, and I think many people might not enjoy having their age displayed like this. The solution I was already thinking about is to add a NEW setting for displaying age, but that seems redundant/extraneous along with the privacy.

We could instead give more explicit hints to the user about what's going on when they set their data. For example, here's a diagram of possible a command flow:

  1. User sets birthday
    1. Birthday privacy == private
      1. "I can announce when it's your birthday if you set your birthday privacy to public"
    2. Birthday privacy == public
      1. "I will announce to your servers when it's your birthday"
      2. Age privacy == private
        1. "I will not show your new age in your birthday announcement"
      3. Age privacy == public
        1. "I will show your new age in your birthday announcement"
  2. User sets birthday privacy
    1. Birthday privacy == private
      1. "I will not announce your birthday. You can change this by setting birthday to public."
    2. Birthday privacy == public
      1. Same as 1.ii.
  3. User sets age privacy
    1. Birthday privacy == private
    2. Birthday privacy == public
      1. Age privacy == private
        1. Same as 1.ii.a.
      2. Age privacy == public
        1. Same as 1.ii.b.

I think this is a state machine. It probably doesn't need to be more complicated than a few nested conditionals but maybe look into that.

Originally posted by @Phanabani in #14 (comment)

Timezone autocomplete

Output top 5 (or more?) fuzzily-matched timezones. Also allow lookup by country name.

Add config option to allow publicly setting personal data

We've had lots of problems on my server with people not understanding command syntax, and it's really difficult to troubleshoot with them when they have to go back and forth to DMs. There should be an option to allow public info changes rather than forcing users into DMs.

I think this is a reasonable option because users still will have the option to DM if they're uncomfortable, and they (should?) have the option of deleting their public messages. Perhaps have a sort of "onboarding" for new users setting info to be like "hey, you can also set your personal info in DMs if you want".

Add format specifier in time/unit conversions to force output unit

Something like {9pm amsterdam}. This might conflict with unit conversion, as there could be an ambiguities.

Input Expected Actual
9 degC 9 degrees Celsius 09:00 in timezone "degC"

Could require either a period specifier (AM/PM) or 24hr time to prevent ambiguity.

Upgrade hook for performing things on Sandpiper upgrades

I'm making privacy fields more important in Sandpiper's operation (age set to public will show it in birthday messages), and I want to notify users about this, so I want to add a way to communicate back to Sandpiper when a database upgrade occurs.

Make birthday notifications atomic

If the bot starts up mid-day, I think it should announce any birthdays for that day even if it's past the birthday midnight. We need to make the birthday announcements atomic then, such that birthdays are announced once and only once, even if the bot restarts.

Embeds with inline fields on Discord for Android are ugly

This is a bug on the android app. I could add a mobile flag to commands to render these messages in monospaced blocks rather than the embeds. I looked into getting the user's mobile status to automatically change rendering style, but this would have way too much overhead (and has unknown behavior if the user is on both mobile and desktop).

Find a better solution for the timezone/unit ambiguity

Currently, it is possible to specify an input timezone like {5pm amsterdam} or {17:00 amsterdam}. {17 amsterdam} or {1700 amsterdam} does not work because we defer to a unit conversion when there is no colon or AM/PM period specified in the numeric section. This rule is not transparent, however, and has been a source of confusion for users.

The reason I've done it this way is because I think specifying a unit is a higher priority action than specifying an input timezone, since it's more common for a user to use their default timezone, and unit conversion is a core feature. Additionally, the fuzzy timezone matcher uses a partial token scorer, which means substring matches have very high precedence in scoring fuzzy matches and will easily match simple unit strings (5 km would 100% match a timezone with a name like "Akmo"). This scorer returned the most natural results during testing, so it is not up for changing.

One solution would be to run another pass at the timezone converter after failing a unit conversion ({17 amsterdam} returns an error "'Amsterdam' is not a recognized unit"), but the downside to this is that unknown unit errors will silently be discarded. I think meaningful error feedback is important and that this solution is undesirable.

Another solution may be to implement a more intelligent algorithm for determining whether a suffix is supposed to be a unit or a timezone. Though, thinking about it just now, that is also undesirable because some units which aren't handled by default have long snake_case names, such as boltzmann_constant. Is there really any way to reliably distinguish between a string like this and a timezone name? I feel it is very important to support uncommon units like this because it feels honestly like magic when the conversion of a weird unit just works out-of-the-box, and as a user myself I really enjoyed that feeling.

I just had another idea, perhaps I could do a similar approach to the two-pass time conversion idea, but for first use the current, more natural partial-token scorer, while the second uses a full token scorer. This retains the algorithm of the current implementation, but adds a very high threshold timezone match as a last resort. If the last resort timezone match fails, we can pretend it didn't happen and raise the unknown unit error. This will increase the domain of valid time conversion inputs without sacrificing units.

Birthday notifications

  • Guild settings database table
    • Birthday announcements channel
  • Role to allow changing guild settings? Just check admin perms
  • User settings table
    • Enable/disable sending their birthday notification
    • Setting to display age in notif? Maybe this could be controlled by age privacy
  • Use user's timezone to decide when to send message (default to UTC)

Improve help messages

they feel very generic

it also feels a bit clunky to have to do for example help timezone, and then look what the commands in it are to then do help timezone set to figure out the formatting for that

It would be cool if it was like a man page where it shows all the commands and their usage by just doing "help birthday"

Examples would be good. Like the stuff you have in GitHub

  • Make less generic
  • Add examples
  • Man page
  • DM only display

Time Conversion - Add "now"

Sometimes I want to know what time it is for people in the server so have a "{now}" conversion would be useful

Onboarding

Create an onboarding system to help make it super easy for users to get started with Piper.

whois command should support tagging

Currently when using "!piper whois" you have to type a name manually e.g. "!piper whois Malivil"
It would be nice to be able to do "!piper whois @Malivil" to take advantage of the auto-complete

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.