Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .env.ci
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,9 @@ HCAPTCHA_SITEKEY=
HCAPTCHA_SECRET=
SLACK_CONTACT_WEBHOOK=

EVENT_DEBUG_LOGGING_ENABLED=1
SLACK_BOT_TOKEN=
SLACK_SIGNING_SECRET=
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=

EVENT_DEBUG_LOGGING_ENABLED=1
9 changes: 7 additions & 2 deletions .env.docker
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ FORCE_SSL=false
LOG_CHANNEL=single
WWWGROUP=1337
LARAVEL_SAIL=1
PHP_RUNTIME=8.1
PHP_RUNTIME=8.3
# PHP_RUNTIME=7.4

DB_CONNECTION=mysql
Expand Down Expand Up @@ -61,6 +61,11 @@ HCAPTCHA_SITEKEY=
HCAPTCHA_SECRET=
SLACK_CONTACT_WEBHOOK=

SLACK_BOT_TOKEN=
SLACK_SIGNING_SECRET=
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=

# The private Eventbrite API key / token for importing events
EVENTBRITE_PRIVATE_TOKEN=

Expand Down Expand Up @@ -88,4 +93,4 @@ EVENT_IMPORTER_MEETUP_GRAPHQL_PRIVATE_KEY_PATH=
# in the past will be returned. That is, start_date = {today's date - EVENTS_API_DEFAULT_DAYS}
EVENTS_API_DEFAULT_DAYS=1

EVENT_DEBUG_LOGGING_ENABLED=1
EVENT_DEBUG_LOGGING_ENABLED=1
7 changes: 6 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

# using Mailtrap (a fake SMTP) will not actually send emails
# using Mailtrap (a fake SMTP) will not actually send emails
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
Expand All @@ -57,6 +57,11 @@ ORGS_API_DOMAIN='https://data.openupstate.org'

GOOGLE_TAG_MANAGER=

SLACK_BOT_TOKEN=
SLACK_SIGNING_SECRET=
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=

HCAPTCHA_SITEKEY=
HCAPTCHA_SECRET=
SLACK_CONTACT_WEBHOOK=
Expand Down
7 changes: 6 additions & 1 deletion .env.testing
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,9 @@ HCAPTCHA_SITEKEY=
HCAPTCHA_SECRET=
SLACK_CONTACT_WEBHOOK=

EVENT_DEBUG_LOGGING_ENABLED=1
SLACK_BOT_TOKEN=
SLACK_SIGNING_SECRET=
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=

EVENT_DEBUG_LOGGING_ENABLED=1
4 changes: 2 additions & 2 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 24

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.3
extensions: :php_psr

- name: copy environment variables to .env
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ npm-debug.log
yarn-error.log
.env
database/database.sqlite*
app-modules/slack-events-bot/slackbot-manifest.dev.json

# IDE helper files. These can be auto generated when running composer update
/_ide_helper.php
Expand All @@ -34,4 +35,4 @@ public/build/
*.pem

# Scribe cache and temporary storage
.scribe
.scribe
6 changes: 3 additions & 3 deletions .tool-versions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our production is still on older versions of PHP and Node.

Once we move things off to Railway, we shouldn't have an issue with versions.

Is changing this likely to mess with anything on the npm or composer packages?

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
nodejs 18.17.1
php 8.1.27
yarn 1.22.19
nodejs 24.3.0
php 8.3.6
yarn 1.22.22
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM serversideup/php:8.1-fpm-nginx
FROM serversideup/php:8.3-fpm-nginx

# Install additional packages
USER root
Expand Down
128 changes: 128 additions & 0 deletions app-modules/slack-events-bot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Slack Events Bot Module

A Laravel module that posts HackGreenville events from the database to configured Slack channels.

## Installation

This module is automatically loaded as it's in the `app-modules` directory.

## Configuration

Add the following environment variables to your `.env` file:

```env
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_SIGNING_SECRET=your-signing-secret
SLACK_CLIENT_ID=your-client-id
SLACK_CLIENT_SECRET=your-client-secret
```

## Setting up Slack Credentials for Testing

To obtain the necessary Slack credentials for testing (SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET, SLACK_CLIENT_ID, SLACK_CLIENT_SECRET), you'll need to create a Slack app and configure it. Follow these steps:

1. Run `cp app-modules/slack-events-bot/slackbot-manifest.json app-modules/slack-events-bot/slackbot-manifest.dev.json` to create a copy version of the manifest. More information about Slack bot manifests can be found on their documentation [here](https://docs.slack.dev/app-manifests/). Modify the values in `slackbot-manifest.dev.json` to match your public endpoint. If you don't have a public endpoint, you may need to create one with [ngrok](https://ngrok.com/).

2. Go to [api.slack.com/apps](https://api.slack.com/apps) and click "Create New App". Then, upload your development manifest to get a head start.

3. Get the following environment variables and add them to your `.env` file: `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`

4. Next, navigate to your public endpoint and add `/slack/install` at the end. You should see a `Add to Slack` button displayed if it worked correctly!

5. Go through the flow to connect the bot to a server and channel accordingly.

6. In your Slack workspace, try doing the `/check_api`, `/add_channel`, and `/remove_channel` commands.

7. The slackbot should work correctly!

## Publishing Configuration

To publish the configuration file:

```bash
php artisan vendor:publish --tag=slack-events-bot-config
```

## Running Migrations

The migrations will run automatically with:

```bash
php artisan migrate
```

## Available Commands

```bash
# Manually check for events and update Slack messages
php artisan slack:check-events

# Delete old messages (default: 90 days)
php artisan slack:delete-old-messages
php artisan slack:delete-old-messages --days=60
```

## Scheduled Tasks

The module automatically schedules:
- Event check: Every hour
- Old message cleanup: Daily

Make sure your Laravel scheduler is running:

```bash
php artisan schedule:work
```

## Slack Commands

The bot supports the following slash commands:

- `/add_channel` - Add the current channel to receive event updates (admin only)
- `/remove_channel` - Remove the current channel from receiving updates (admin only)
- `/check_api` - Manually trigger an event check (rate limited to once per 15 minutes per workspace)

## Routes

- `GET /slack/install` - Display Slack installation button
- `GET /slack/auth` - OAuth callback for Slack
- `POST /slack/events` - Webhook endpoint for Slack events and commands

## Features

- Posts weekly event summaries to configured Slack channels
- Automatically updates messages when events change
- Handles message chunking for large event lists
- Rate limiting for manual checks
- Admin-only channel management
- OAuth installation flow
- Automatic cleanup of old messages
- Direct database integration (no API calls needed)

## How It Works

1. The bot queries the Event model directly every hour for new/updated events
2. Events are filtered to show published events from 1 day ago to 14 days ahead
3. Events are grouped by week (Sunday to Saturday)
4. Messages are posted/updated in configured Slack channels
5. If a week has many events, they're split across multiple messages
6. Messages for the current week and next week (5 days early) are maintained

## Configuration Options

The module can be configured in `config/slack-events-bot.php`:

- `days_to_look_back` - How many days in the past to include events (default: 1)
- `days_to_look_ahead` - How many days in the future to include events (default: 14)
- `max_message_character_length` - Maximum characters per Slack message (default: 3000)
- `check_api_cooldown_minutes` - Cooldown period for manual checks (default: 15)
- `old_messages_retention_days` - Days to keep old messages (default: 90)

## Migration from Python

This module is a Laravel port of the original Python slack-events-bot, now refactored to use the Event model directly instead of making API calls. This provides:

- Better performance (no HTTP overhead)
- Real-time data (no API caching delays)
- Tighter integration with the application
- Easier maintenance and debugging
25 changes: 25 additions & 0 deletions app-modules/slack-events-bot/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "hack-greenville/slack-events-bot",
"description": "Slack bot that relays information from HackGreenville Labs' Events API to Slack channels",
"type": "library",
"license": "MIT",
"version": "1.0",
"autoload": {
"psr-4": {
"HackGreenville\\SlackEventsBot\\": "src/",
"HackGreenville\\SlackEventsBot\\Database\\Factories\\": "./database/factories/"
}
},
"autoload-dev": {
"psr-4": {
"HackGreenville\\SlackEventsBot\\Tests\\": "tests/"
}
},
"extra": {
"laravel": {
"providers": [
"HackGreenville\\SlackEventsBot\\Providers\\SlackEventsBotServiceProvider"
]
}
}
}
60 changes: 60 additions & 0 deletions app-modules/slack-events-bot/config/slack-events-bot.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

return [
/*
|--------------------------------------------------------------------------
| Slack Bot Configuration
|--------------------------------------------------------------------------
|
| These are the configuration values for the Slack Events Bot.
|
*/

'bot_token' => env('SLACK_BOT_TOKEN'),
'signing_secret' => env('SLACK_SIGNING_SECRET'),
'client_id' => env('SLACK_CLIENT_ID'),
'client_secret' => env('SLACK_CLIENT_SECRET'),

/*
|--------------------------------------------------------------------------
| Scopes
|--------------------------------------------------------------------------
*/
'scopes' => [
'chat:write',
'chat:write.public',
'commands',
'incoming-webhook',
'users:read',
],

/*
|--------------------------------------------------------------------------
| Message Configuration
|--------------------------------------------------------------------------
*/
'max_message_character_length' => 3000,
'header_buffer_length' => 61,

/*
|--------------------------------------------------------------------------
| Cooldown Configuration
|--------------------------------------------------------------------------
*/
'check_api_cooldown_minutes' => 15,

/*
|--------------------------------------------------------------------------
| Database Configuration
|--------------------------------------------------------------------------
*/
'old_messages_retention_days' => 90,

/*
|--------------------------------------------------------------------------
| Event Configuration
|--------------------------------------------------------------------------
*/
'days_to_look_back' => 1,
'days_to_look_ahead' => 14,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace HackGreenville\SlackEventsBot\Database\Factories;

use HackGreenville\SlackEventsBot\Models\SlackChannel;
use HackGreenville\SlackEventsBot\Models\SlackWorkspace;
use Illuminate\Database\Eloquent\Factories\Factory;

class SlackChannelFactory extends Factory
{
protected $model = SlackChannel::class;

public function definition(): array
{
return [
'slack_channel_id' => $this->faker->unique()->regexify('[C][A-Z0-9]{10}'),
'slack_workspace_id' => SlackWorkspace::factory(),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace HackGreenville\SlackEventsBot\Database\Factories;

use HackGreenville\SlackEventsBot\Models\SlackChannel;
use HackGreenville\SlackEventsBot\Models\SlackMessage;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;

class SlackMessageFactory extends Factory
{
protected $model = SlackMessage::class;

public function definition(): array
{
return [
'week' => Carbon::now()->startOfWeek(),
'message' => $this->faker->sentence,
'message_timestamp' => now()->timestamp . '.' . $this->faker->randomNumber(6, true),
'sequence_position' => $this->faker->numberBetween(0, 10),
'channel_id' => SlackChannel::factory(),
];
}
}
Loading