title: [Go] Build a Smart Garbage Truck LINE Bot with Go + Gemini + GCP: A Complete Solution from Query to Reminder
published: false
date: 2025-11-01 00:00:00 UTC
tags:
canonical_url: https://www.evanlin.com/til-linebot-garbage-helper/
---

# Preface
In Taiwan, the garbage truck schedule is always unpredictable. You remember it coming at seven o'clock last night, but you're still waiting today; or you just went out to take out the trash, and the garbage truck just passed by. I believe this is a common problem for many people.
With the development of smart cities, more and more cities are starting to provide real-time garbage truck information APIs, but this information is not easy for the general public to use.
At this time, I saw a [friend's post](https://www.facebook.com/yukaihuangtw/posts/pfbid02Hf5K28V7BmBcy9FzHBdu9r8zD5TjtK3MTKL4BpwMdX34Wc9SP1ktoZfvTGQTRix5l) on Facebook, where he described that he had created a website for tracking garbage trucks. ([Website](https://garbage.yukai.dev/), [github](https://github.com/Yukaii/garbage/))

At this time, I was thinking, couldn't we combine a LINE Bot to create a tool that could quickly help others? Therefore, I decided to build a garbage truck LINE Bot so that everyone can easily query garbage truck information and even set up reminder notifications through the most familiar communication software. More importantly, this Bot is not just a simple command query, but integrates Google Gemini AI, which can understand natural language like "Where can I throw away garbage before 7 pm tonight?" to provide a truly intelligent service experience.
### Project Code:
[https://github.com/kkdai/linebot-garbage-helper](https://github.com/kkdai/linebot-garbage-helper)
(Through this code, you can quickly deploy to GCP Cloud Run and use Cloud Build to achieve automated CI/CD)
## ποΈ Project Feature Introduction
### Core Features
1. **ποΈ Real-time Garbage Truck Query**
* Enter an address or share your location to query nearby garbage truck stops
* Displays estimated arrival time, route information, and Google Maps navigation links
2. **β° Smart Reminder System**
* Can set a reminder N minutes before the garbage truck arrives
* Automatically push notifications, so you'll never miss the garbage truck again
* Supports multiple reminder status management (active, sent, expired, cancelled)
3. **π€ Natural Language Query**
* Integrates Google Gemini AI, supports natural language understanding
* For example: "Where can I throw away garbage in Da'an District, Taipei City before 7 pm?"
* Automatically extracts location, time range, and other query conditions
4. **β€οΈ Favorite Location Feature**
* Saves frequently used locations (home, company, etc.)
* Quickly query garbage truck information for favorite locations
## ποΈ Technical Architecture Explanation
### Why Choose the Go Language?
1. **Excellent Concurrency Handling**: Go's goroutines are very suitable for handling a large number of webhook requests
2. **Fast Compilation and Deployment**: Especially suitable for containerized deployment on Cloud Run
3. **Rich Ecosystem**: LINE Bot SDK and Google Cloud SDK both have official support
4. **Excellent Performance**: Low memory usage, fast startup speed
### System Architecture Diagram
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β LINE Client βββββΆβ Cloud Run βββββΆβ Firestore β
βββββββββββββββββββ β (Go App) β β (Database) β
ββββββββββββββββββββ βββββββββββββββββββ
β
βΌ
ββββββββββββββββββββ
β External APIs β
β β’ Google Maps β
β β’ Gemini AI β
β β’ Garbage Truck Data Source β
ββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββ
β Cloud Scheduler β
β (Reminder Scheduling Trigger) β
ββββββββββββββββββββ
### Main Technology Stack
- **Language**: Go 1.24
- **Cloud Platform**: Google Cloud Platform
- **Database**: Firestore (NoSQL Document Database)
- **External APIs**: LINE Bot SDK, Google Maps API, Gemini API
- **Data Source**: [Yukaii/garbage](https://github.com/Yukaii/garbage)
- **Deployment**: Cloud Run + Cloud Build
## π» Core Feature Implementation
### 1. LINE Webhook Handling
First, let's see how to handle LINE's webhook events:
func (h *Handler) HandleWebhook(w http.ResponseWriter, r *http.Request) {
log.Printf("Webhook received from %s", r.RemoteAddr)
events, err := webhook.ParseRequest(h.channelSecret, r)
if err != nil {
log.Printf("Error parsing webhook request: %v", err)
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
ctx := r.Context()
for _, event := range events {
go h.handleEvent(ctx, event) // Use goroutine to handle events
}
w.WriteHeader(http.StatusOK)
}
### 2. Gemini AI Natural Language Understanding
This is the most interesting part of the entire system, using Gemini to understand the user's natural language queries:
func (gc *GeminiClient) AnalyzeIntent(ctx context.Context, userMessage string) (*IntentResult, error) {
model := gc.client.GenerativeModel(gc.model)
prompt := fmt.Sprintf(`You are a query intent analyzer, specializing in analyzing user queries about garbage trucks.
User input may contain place names and times. Please analyze the input and output the results in JSON format.
Output format:
{
"district": "District Name (if any)",
"time_window": {
"from": "Start time (HH:MM format, if any)",
"to": "End time (HH:MM format, if any)"
},
"keywords": ["Keyword array"],
"query_type": "garbage_truck_eta"
}
Example:
Input: "Where can I throw away garbage in Da'an District, Taipei City before 7 pm tonight?"
Output:
{
"district": "Da'an District, Taipei City",
"time_window": {
"from": "",
"to": "19:00"
},
"keywords": ["Taipei City", "Da'an District", "throw away garbage", "tonight", "7 pm"],
"query_type": "garbage_truck_eta"
}
Please analyze the following user input:
"%s"
Please only return JSON, do not include any other explanatory text.`, userMessage)
resp, err := model.GenerateContent(ctx, genai.Text(prompt))
if err != nil {
return nil, err
}
// Parse JSON response
var result IntentResult
if err := json.Unmarshal([]byte(responseText), &result); err != nil {
// If Gemini cannot parse, fall back to simple keyword matching
return &IntentResult{
District: extractDistrict(userMessage),
Keywords: []string{userMessage},
QueryType: "garbage_truck_eta",
}, nil
}
return &result, nil
}
### 3. Smart Reminder Scheduling System
The reminder system is one of the core features of this project, and its design considers reliability and performance:
func (s *Scheduler) ProcessReminders(ctx context.Context) error {
now := time.Now()
// Performance optimization: Check if there are active reminders first
count, err := s.store.CountActiveReminders(ctx)
if err != nil {
log.Printf("Warning: failed to count active reminders: %v", err)
} else if count == 0 {
log.Printf("No active reminders, skipping processing")
return nil
}
reminders, err := s.store.GetActiveReminders(ctx, now)
if err != nil {
return fmt.Errorf("failed to get active reminders: %w", err)
}
log.Printf("Found %d active reminders to process", len(reminders))
for _, reminder := range reminders {
notificationTime := reminder.ETA.Add(-time.Duration(reminder.AdvanceMinutes) * time.Minute)
// Check if it's time to send the reminder
if now.Before(notificationTime) {
continue // Not yet time to send
}
if now.After(reminder.ETA) {
// ETA has expired, mark as expired
s.store.UpdateReminderStatus(ctx, reminder.ID, "expired")
continue
}
// Send reminder notification
if err := s.sendReminderNotification(ctx, reminder); err != nil {
log.Printf("Error sending reminder %s: %v", reminder.ID, err)
continue
}
// Update status to sent
s.store.UpdateReminderStatus(ctx, reminder.ID, "sent")
}
return nil
}
### 4. Firestore Data Structure Design
We use Firestore to store user data and reminder information:
type Reminder struct {
ID string firestore:"id"
UserID string firestore:"userId"
StopName string firestore:"stopName"
RouteID string firestore:"routeId"
ETA time.Time firestore:"eta"
AdvanceMinutes int firestore:"advanceMinutes"
Status string firestore:"status" // active, sent, expired, cancelled
CreatedAt time.Time firestore:"createdAt"
UpdatedAt time.Time firestore:"updatedAt"
}
type User struct {
ID string firestore:"id"
Favorites []Favorite firestore:"favorites"
CreatedAt time.Time firestore:"createdAt"
UpdatedAt time.Time firestore:"updatedAt"
}
### 5. Dual-Protection Reminder Mechanism
To ensure that reminders are not missed, the system is designed with a dual-protection mechanism:
1. **Local Scheduler**: The background scheduling service starts automatically when the application starts
2. **External Trigger**: Calls `/tasks/dispatch-reminders` periodically through Cloud Scheduler
// Local scheduler
func (s *Scheduler) StartScheduler(ctx context.Context) {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
cleanupTicker := time.NewTicker(1 * time.Hour)
defer cleanupTicker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
s.ProcessReminders(ctx)
case <-cleanupTicker.C:
s.CleanupExpiredReminders(ctx)
}
}
}
// External trigger endpoint
r.HandleFunc("/tasks/dispatch-reminders", func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "Bearer "+cfg.InternalTaskToken {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if err := reminderService.ProcessReminders(r.Context()); err != nil {
log.Printf("Error processing reminders: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
})
## π Cloud Build Automated Deployment
### Setting up Cloud Build Triggers
The deployment process is fully automated, and deployment is automatically triggered as long as the code is pushed to the main branch:
cloudbuild.yaml
steps:
# Build the container image
- name: 'gcr.io/cloud-builders/docker' args: ['build', '-t', 'gcr.io/$PROJECT_ID/garbage-linebot:$COMMIT_SHA', '.']
# Push the container image to Container Registry
- name: 'gcr.io/cloud-builders/docker' args: ['push', 'gcr.io/$PROJECT_ID/garbage-linebot:$COMMIT_SHA']
# Deploy container image to Cloud Run
- name: 'gcr.io/cloud-builders/gcloud'
args:
- 'run'
- 'deploy'
- 'garbage-linebot'
- '--image'
- 'gcr.io/$PROJECT_ID/garbage-linebot:$COMMIT_SHA'
- '--region'
- 'asia-east1'
- '--platform'
- 'managed'
- '--allow-unauthenticated'
- '--set-env-vars'
- 'LINE_CHANNEL_SECRET=${_LINE_CHANNEL_SECRET},LINE_CHANNEL_ACCESS_TOKEN=${_LINE_CHANNEL_ACCESS_TOKEN},GOOGLE_MAPS_API_KEY=${_GOOGLE_MAPS_API_KEY},GEMINI_API_KEY=${_GEMINI_API_KEY},GCP_PROJECT_ID=$PROJECT_ID'
### Environment Variable Settings
Set substitution variables in the Cloud Build trigger:
_LINE_CHANNEL_SECRET: your_line_channel_secret
_LINE_CHANNEL_ACCESS_TOKEN: your_line_channel_access_token
_GOOGLE_MAPS_API_KEY: your_google_maps_api_key
_GEMINI_API_KEY: your_gemini_api_key
In particular, `INTERNAL_TASK_TOKEN` will be automatically generated when the application starts, no manual configuration is required!
## π§ Challenges and Solutions Encountered
### 1. Gemini API Stability Handling
**Problem**: The Gemini API occasionally returns responses in a non-JSON format, causing parsing to fail.
**Solution**: Implement error handling and fallback mechanism:
var result IntentResult
if err := json.Unmarshal([]byte(responseText), &result); err != nil {
// If Gemini cannot parse, fall back to simple keyword matching
return &IntentResult{
District: extractDistrict(userMessage),
Keywords: []string{userMessage},
QueryType: "garbage_truck_eta",
TimeWindow: TimeWindow{From: "", To: ""},
}, nil
}
### 2. Firestore Query Performance Optimization
**Problem**: Querying all active reminders every time causes unnecessary data reads.
**Solution**: Add a count query as an early return optimization:
// Check if there are active reminders first
count, err := s.store.CountActiveReminders(ctx)
if count == 0 {
log.Printf("No active reminders, skipping processing")
return nil
}
### 3. Cloud Scheduler Region Configuration Issues
**Problem**: Cloud Scheduler may not be supported in some regions, causing automatic reminders to fail.
**Solution**: Design a dual-protection mechanism so that even if the external scheduler fails, the local scheduler can still operate normally.
### 4. LINE Bot Message Push Limits
**Problem**: LINE Bot has frequency limits for pushing messages.
**Solution**:
- Implement reminder status management to avoid duplicate sending
- Add appropriate error handling and retry mechanisms
- Use goroutines to handle asynchronously to avoid blocking the main process
## π Performance Monitoring and Reliability
### Health Check Endpoint
r.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}).Methods("GET")
### Detailed Log Records
The system has detailed log records at key nodes for easy debugging and monitoring:
log.Printf("Processing reminder %s: ETA=%s, NotificationTime=%s, AdvanceMinutes=%d",
reminder.ID, reminder.ETA.Format("2006-01-02 15:04:05"),
notificationTime.Format("2006-01-02 15:04:05"), reminder.AdvanceMinutes)
## π― Summary and Future Improvements
This garbage truck LINE Bot project demonstrates how to combine modern technology stacks to solve real-life problems. Through the high performance of the Go language, the natural language understanding of Gemini AI, and the cloud services of GCP, we have created a solution that is both practical and intelligent.
### Project Highlights
1. **Intelligent Query**: Understands natural language through Gemini AI, providing a more user-friendly experience
2. **Reliable Reminder System**: Dual-protection mechanism ensures that important notifications are not missed
3. **Modern Architecture**: Uses a microservices architecture, easy to scale and maintain
4. **Automated Deployment**: Complete CI/CD process, reducing maintenance costs
### Future Improvement Directions
1. **Performance Optimization**
- Create Firestore composite indexes to improve query performance
- Implement batch push to reduce API calls
2. **Reliability Enhancement**
- Add distributed locks to avoid duplicate execution
- Implement exponential backoff retry mechanism
3. **Feature Expansion**
- Support garbage truck data for more cities
- Add usage statistics and analysis functions
- Integrate more AI capabilities, such as image recognition
4. **Monitoring Enhancement**
- Integrate Prometheus/OpenTelemetry
- Create a complete performance monitoring dashboard
Through this project, I have deeply realized the advantages of the Go language in cloud-native application development and how AI technology can make traditional applications more intelligent. I hope this experience sharing can help developers who are learning related technologies!
### Related Resources
- [Project GitHub Repository](https://github.com/kkdai/linebot-garbage-helper)
- [LINE Bot SDK for Go](https://github.com/line/line-bot-sdk-go)
- [Google Gemini AI API](https://ai.google.dev/)
- [GCP Cloud Run Documentation](https://cloud.google.com/run/docs)
- [Firestore Documentation](https://cloud.google.com/firestore/docs)
Top comments (0)