LINE to Google Drive Integration#
Learn how to automatically transfer files from LINE messaging platform to Google Drive with smart organization and processing capabilities.
π― What You'll Build#
A file automation system that: - Receives files (images, documents, videos) via LINE messaging - Automatically downloads and categorizes files - Organizes files in Google Drive with intelligent folder structure - Processes and analyzes file content - Sends confirmation notifications back to LINE - Maintains file inventory and metadata
π Requirements#
- LINE Developers account with Messaging API
- Google Cloud project with Drive API enabled
- Google Service Account for Drive access
- n8n instance running with internet access
π§ Workflow Overview#
Key Components#
- LINE Webhook Trigger - Receives messages and files
- File Downloader - Downloads files from LINE servers
- File Analyzer - Determines file type and metadata
- Drive Uploader - Uploads to Google Drive with organization
- Notification Sender - Confirms successful transfer
- Database Logger - Tracks file transfers and metadata
π Step-by-Step Guide#
1. Set Up LINE Integration#
-
Configure LINE Developers Console - Create Messaging API channel - Enable webhook endpoint - Get Channel Access Token - Configure webhook URL to your n8n instance
-
Set Up LINE Trigger
1 2 3 4 5 6
// LINE Webhook Trigger Configuration { webhookUrl: 'https://your-n8n-instance.com/webhook/line-drive', channelAccessToken: 'YOUR_CHANNEL_ACCESS_TOKEN', responseMode: 'respondOnReceive' }
2. Handle File Messages#
-
Detect File Messages
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
// Process incoming LINE messages function processLineMessage(event) { if (event.message.type === 'image') { return { type: 'image', messageId: event.message.id, userId: event.source.userId, timestamp: event.timestamp }; } else if (event.message.type === 'file') { return { type: 'file', messageId: event.message.id, fileName: event.message.fileName, fileSize: event.message.fileSize, userId: event.source.userId, timestamp: event.timestamp }; } else if (event.message.type === 'video') { return { type: 'video', messageId: event.message.id, duration: event.message.duration, userId: event.source.userId, timestamp: event.timestamp }; } return null; // Not a file message } -
Download Files from LINE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// Download file content from LINE servers async function downloadFileFromLine(messageId, type) { const lineApiUrl = 'https://api-data.line.me/v2/bot/message/' + messageId + '/content'; try { const response = await fetch(lineApiUrl, { headers: { 'Authorization': 'Bearer ' + process.env.LINE_CHANNEL_ACCESS_TOKEN } }); if (!response.ok) { throw new Error(`LINE API Error: ${response.status} ${response.statusText}`); } const buffer = await response.arrayBuffer(); return Buffer.from(buffer); } catch (error) { console.error('Error downloading file from LINE:', error); throw error; } }
3. File Analysis and Categorization#
-
Determine File Type and Category
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
// Analyze file and determine appropriate category function analyzeFile(fileBuffer, fileInfo) { const analysis = { originalType: fileInfo.type, size: fileBuffer.length, timestamp: new Date().toISOString() }; // Enhanced file type detection if (fileInfo.type === 'image') { analysis.category = 'Images'; analysis.subcategory = 'Photos'; // Detect image dimensions (if possible) const dimensions = getImageDimensions(fileBuffer); if (dimensions) { analysis.width = dimensions.width; analysis.height = dimensions.height; } } else if (fileInfo.type === 'file') { const extension = fileInfo.fileName.split('.').pop().toLowerCase(); if (['pdf', 'doc', 'docx', 'txt'].includes(extension)) { analysis.category = 'Documents'; analysis.subcategory = 'Text Documents'; } else if (['xls', 'xlsx', 'csv'].includes(extension)) { analysis.category = 'Documents'; analysis.subcategory = 'Spreadsheets'; } else if (['ppt', 'pptx'].includes(extension)) { analysis.category = 'Documents'; analysis.subcategory = 'Presentations'; } else { analysis.category = 'Other Files'; analysis.subcategory = 'General'; } } else if (fileInfo.type === 'video') { analysis.category = 'Videos'; analysis.subcategory = 'General'; analysis.duration = fileInfo.duration; } return analysis; } -
Generate File Metadata
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// Create comprehensive file metadata function generateFileMetadata(analysis, lineInfo) { return { // Source information source: 'LINE', userId: lineInfo.userId, originalMessageId: lineInfo.messageId, receivedAt: new Date(lineInfo.timestamp).toISOString(), // File information category: analysis.category, subcategory: analysis.subcategory, fileSize: analysis.size, originalFileName: lineInfo.fileName || `${lineInfo.messageId}.${getFileExtension(analysis.originalType)}`, // Processing information processedAt: analysis.timestamp, processingVersion: '1.0', // Additional metadata tags: generateTags(analysis, lineInfo), description: generateDescription(analysis) }; }
4. Google Drive Integration#
-
Organize Folder Structure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Create organized folder structure function generateFolderPath(metadata) { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); // Create user-specific folder if available const userFolder = metadata.userId ? `user_${metadata.userId}` : 'anonymous'; return { basePath: `LINE Files`, yearPath: `${year}`, monthPath: `${year}-${month}`, categoryPath: metadata.category, userPath: userFolder, fullPath: `LINE Files/${year}/${year}-${month}/${metadata.category}/${userFolder}` }; } -
Upload to Google Drive
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
// Upload file to Google Drive with metadata async function uploadToGoogleDrive(fileBuffer, metadata) { const { google } = require('googleapis'); const auth = new google.auth.GoogleAuth({ keyFile: 'path/to/service-account.json', scopes: ['https://www.googleapis.com/auth/drive'] }); const drive = google.drive({ version: 'v3', auth }); const folderPath = generateFolderPath(metadata); // Ensure folders exist const folderId = await ensureFolderStructure(drive, folderPath); // Upload file const fileMetadata = { name: sanitizeFileName(metadata.originalFileName), parents: [folderId], appProperties: { source: 'LINE', userId: metadata.userId, category: metadata.category }, properties: { originalMessageId: metadata.originalMessageId, receivedAt: metadata.receivedAt, processedAt: metadata.processedAt } }; const media = { mimeType: getMimeType(metadata.originalFileName), body: require('stream').Readable.from(fileBuffer) }; try { const response = await drive.files.create({ resource: fileMetadata, media: media, fields: 'id,name,webViewLink,size,createdTime' }); return { fileId: response.data.id, fileName: response.data.name, viewUrl: response.data.webViewLink, size: response.data.size, createdTime: response.data.createdTime }; } catch (error) { console.error('Google Drive upload error:', error); throw error; } } -
Folder Structure Management
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
// Ensure folder structure exists in Google Drive async function ensureFolderStructure(drive, folderPath) { const pathParts = folderPath.fullPath.split('/'); let currentFolderId = 'root'; // Start from root for (const folderName of pathParts) { // Check if folder exists const existingFolder = await findFolderByName(drive, folderName, currentFolderId); if (existingFolder) { currentFolderId = existingFolder.id; } else { // Create new folder const newFolder = await drive.files.create({ resource: { name: folderName, mimeType: 'application/vnd.google-apps.folder', parents: [currentFolderId] }, fields: 'id' }); currentFolderId = newFolder.data.id; } } return currentFolderId; } async function findFolderByName(drive, folderName, parentId) { try { const response = await drive.files.list({ q: `name='${folderName}' and mimeType='application/vnd.google-apps.folder' and trashed=false and '${parentId}' in parents`, fields: 'files(id,name)' }); return response.data.files.length > 0 ? response.data.files[0] : null; } catch (error) { console.error('Error finding folder:', error); return null; } }
5. Smart File Processing#
-
Image Processing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
// Process images with additional features async function processImage(fileBuffer, metadata) { const sharp = require('sharp'); try { const image = sharp(fileBuffer); const metadata_image = await image.metadata(); // Generate thumbnail const thumbnail = await image .resize(200, 200, { fit: 'inside' }) .jpeg({ quality: 80 }) .toBuffer(); // Extract EXIF data if available let exifData = null; if (metadata_image.exif) { exifData = await extractExifData(fileBuffer); } return { originalWidth: metadata_image.width, originalHeight: metadata_image.height, format: metadata_image.format, thumbnail: thumbnail, exifData: exifData, dominantColor: await extractDominantColor(fileBuffer) }; } catch (error) { console.error('Image processing error:', error); return null; } } -
Document Processing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
// Process documents for text extraction async function processDocument(fileBuffer, metadata) { const pdfParse = require('pdf-parse'); const mammoth = require('mammoth'); try { let textContent = ''; const extension = metadata.originalFileName.split('.').pop().toLowerCase(); if (extension === 'pdf') { const data = await pdfParse(fileBuffer); textContent = data.text; } else if (extension === 'docx') { const result = await mammoth.extractRawText({ buffer: fileBuffer }); textContent = result.value; } else if (extension === 'txt') { textContent = fileBuffer.toString('utf-8'); } // Extract key information from text const extractedInfo = await extractKeyInformation(textContent); return { textContent: textContent.substring(0, 1000), // First 1000 chars wordCount: textContent.split(' ').length, extractedInfo: extractedInfo }; } catch (error) { console.error('Document processing error:', error); return null; } }
6. Notification System#
-
Send Confirmation to LINE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// Send confirmation message back to user async function sendLineConfirmation(userId, uploadResult, metadata) { const confirmationMessage = { type: 'text', text: `β File uploaded successfully!\n\n` + `π File: ${metadata.originalFileName}\n` + `π Category: ${metadata.category}\n` + `π Size: ${formatFileSize(metadata.size)}\n` + `π View: ${uploadResult.viewUrl}\n\n` + `File has been organized in your Google Drive!` }; // Add thumbnail for images if (metadata.category === 'Images' && uploadResult.thumbnail) { const lineApiUrl = 'https://api-data.line.me/v2/bot/message/upload'; // Upload thumbnail and create rich message } await sendLineMessage(userId, confirmationMessage); } -
Send Rich Messages
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// Create rich message with image preview function createRichMessage(uploadResult, metadata) { if (metadata.category === 'Images') { return { type: 'image', originalContentUrl: uploadResult.viewUrl, previewImageUrl: uploadResult.thumbnailUrl || uploadResult.viewUrl }; } else { return { type: 'template', altText: 'File uploaded successfully', template: { type: 'buttons', text: `File "${metadata.originalFileName}" uploaded to Google Drive`, actions: [ { type: 'uri', label: 'View File', uri: uploadResult.viewUrl }, { type: 'postback', label: 'Share', data: `action=share&fileId=${uploadResult.fileId}` } ] } }; } }
π Advanced Features#
Duplicate Detection#
- File Hashing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
// Generate file hash for duplicate detection const crypto = require('crypto'); function generateFileHash(fileBuffer) { return crypto.createHash('sha256').update(fileBuffer).digest('hex'); } // Check for duplicates async function checkForDuplicates(fileHash, drive, metadata) { const query = `appProperties has { key='fileHash' and value='${fileHash}' } and trashed=false`; try { const response = await drive.files.list({ q: query, fields: 'files(id,name,createdTime)' }); if (response.data.files.length > 0) { return { isDuplicate: true, existingFile: response.data.files[0] }; } return { isDuplicate: false }; } catch (error) { console.error('Duplicate check error:', error); return { isDuplicate: false }; } }
File Sharing Automation#
- Smart Sharing Rules
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
// Implement smart sharing based on content async function applySharingRules(uploadResult, metadata, processedContent) { const { google } = require('googleapis'); const auth = new google.auth.GoogleAuth({ keyFile: 'path/to/service-account.json', scopes: ['https://www.googleapis.com/auth/drive'] }); const drive = google.drive({ version: 'v3', auth }); // Define sharing rules const sharingRules = [ { condition: (meta) => meta.category === 'Documents' && meta.subcategory === 'Spreadsheets', action: 'shareWithTeam', emails: ['[email protected]'] }, { condition: (meta) => meta.tags.includes('invoice'), action: 'shareWithFinance', emails: ['[email protected]'] } ]; for (const rule of sharingRules) { if (rule.condition(metadata)) { await shareFile(drive, uploadResult.fileId, rule.emails, 'reader'); } } } async function shareFile(drive, fileId, emails, role) { for (const email of emails) { await drive.permissions.create({ fileId: fileId, requestBody: { type: 'user', role: role, emailAddress: email } }); } }
Content Analysis#
- AI-Powered Content Analysis
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// Analyze image content with AI async function analyzeImageContent(imageBuffer) { const vision = require('@google-cloud/vision'); const client = new vision.ImageAnnotatorClient(); try { const [result] = await client.labelDetection(imageBuffer); const labels = result.labelAnnotations.map(label => ({ description: label.description, confidence: label.score })); const [textResult] = await client.textDetection(imageBuffer); const text = textResult.textAnnotations[0]?.description || ''; return { labels: labels, extractedText: text, safeSearch: (await client.safeSearchDetection(imageBuffer))[0].safeSearchAnnotation }; } catch (error) { console.error('Vision API error:', error); return null; } }
π§ͺ Testing and Monitoring#
Test Scenarios#
- File Upload Testing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// Test different file types const testFiles = [ { type: 'image', url: 'https://example.com/test.jpg' }, { type: 'document', url: 'https://example.com/test.pdf' }, { type: 'video', url: 'https://example.com/test.mp4' } ]; async function testFileUpload(testFile) { const startTime = Date.now(); try { const result = await processFileUpload(testFile); const duration = Date.now() - startTime; return { success: true, duration: duration, result: result }; } catch (error) { return { success: false, duration: Date.now() - startTime, error: error.message }; } }
Performance Monitoring#
- Upload Metrics
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// Track upload performance function trackUploadMetrics(uploadResult, metadata, processingTime) { const metrics = { timestamp: new Date().toISOString(), fileSize: metadata.size, processingTime: processingTime, category: metadata.category, success: uploadResult.success, error: uploadResult.error || null }; // Send to monitoring system sendMetricsToMonitoring(metrics); return metrics; }
π Troubleshooting#
Common Issues#
LINE API Errors - Check webhook URL is accessible - Verify Channel Access Token - Monitor rate limits - Check message format
Google Drive Issues - Verify service account permissions - Check Drive API quota - Validate folder structure - Handle large file uploads
File Processing Errors - Unsupported file formats - Corrupted files - Memory issues with large files - Encoding problems
Debug Tools#
- Comprehensive Logging
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Add detailed logging function logFileProcessing(metadata, stage, data, error = null) { const logEntry = { timestamp: new Date().toISOString(), messageId: metadata.originalMessageId, stage: stage, userId: metadata.userId, data: data, error: error ? error.message : null }; console.log(JSON.stringify(logEntry)); // Optionally send to logging service }
π Scaling Considerations#
High Volume Processing#
-
Queue System
1 2 3 4 5 6 7 8
// Implement job queue for processing const Queue = require('bull'); const fileProcessingQueue = new Queue('file processing'); fileProcessingQueue.process(async (job) => { const { metadata, fileBuffer } = job.data; return await processFile(metadata, fileBuffer); }); -
Caching Strategy
1 2 3 4 5 6 7 8 9 10 11
// Cache frequently accessed data const NodeCache = require('node-cache'); const cache = new NodeCache({ stdTTL: 600 }); // 10 minutes function getCachedFolderStructure(path) { return cache.get(path); } function setCachedFolderStructure(path, folderId) { cache.set(path, folderId); }
Related Tutorials: - LINE Messaging API - Basic LINE integration - Google Sheets Integration - Google services setup
Resources: - LINE Messaging API Documentation - Google Drive API Documentation - n8n LINE Integration