Conversation
Merge pull request znanx#21 from Kiznaiverr/moon
There was a problem hiding this comment.
Pull request overview
This PR adds an auto group open/close feature that allows group admins to schedule when WhatsApp groups should automatically open or close. The feature uses time-based scheduling with per-group configuration and runs on a cron job every minute to check and update group statuses.
- Introduces scheduled auto group open/close with configurable time ranges (e.g., 14:30-15:00)
- Adds commands for setting schedules (
.setautoclose) and toggling the feature (.autoclose on/off) - Integrates cron-based scheduler that runs every minute to enforce configured schedules
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
plugins/admin/setautoclose.js |
New command plugin for configuring auto group open/close time schedules |
lib/system/autoclose.js |
New scheduler module implementing cron-based automatic group status updates |
plugins/admin/moderation.js |
Extended moderation commands to support toggling autoclose on/off |
plugins/group/groupinfo.js |
Updated group info display to show autoclose status and schedule |
lib/system/models.js |
Added autoclose data model to group schema |
lib/system/listeners.js |
Initialized autoclose scheduler on bot startup and updated image path |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const groupSet = global.db.groups[x.jid] | ||
| if (!groupSet) return | ||
| const pic = await conn.sock.profilePictureUrl(x.member, 'image') || await Func.fetchBuffer('./src/image/default.jpg') | ||
| const pic = await conn.sock.profilePictureUrl(x.member, 'image') || await Func.fetchBuffer('./lib/assets/images/default.jpg') |
There was a problem hiding this comment.
This file path change from './src/image/default.jpg' to './lib/assets/images/default.jpg' appears to be unrelated to the auto group open/close feature. The directory './lib/assets/images/' does not exist in the codebase, while './src/image/default.jpg' is used consistently in other files. This change should either be removed or done in a separate PR with proper file migration.
| const pic = await conn.sock.profilePictureUrl(x.member, 'image') || await Func.fetchBuffer('./lib/assets/images/default.jpg') | |
| const pic = await conn.sock.profilePictureUrl(x.member, 'image') || await Func.fetchBuffer('./src/image/default.jpg') |
| const currMin = curr[0] * 60 + curr[1] | ||
| const startMin = st[0] * 60 + st[1] | ||
| const endMin = ed[0] * 60 + ed[1] | ||
|
|
There was a problem hiding this comment.
The time range comparison logic does not handle cross-midnight scenarios. If a group should be closed from 23:00 to 01:00, the current implementation would incorrectly evaluate this range. When endMin is less than startMin (indicating a cross-midnight range), the logic should check if currMin is either greater than or equal to startMin OR less than endMin.
| // Handle ranges that cross midnight (e.g., 23:00–01:00) | |
| if (endMin < startMin) { | |
| return currMin >= startMin || currMin < endMin | |
| } | |
| // Normal same-day range |
| // Close group if time is within range | ||
| if (shouldBeClosed && !isClosed) { | ||
| try { | ||
| await conn.groupSettingUpdate(jid, 'announcement') | ||
| groupSet.autoclose.isClosed = true | ||
| conn.reply(jid, Func.texted('bold', `🚩 Group status changed CLOSED due to autoclose set ${start}-${end}`)) | ||
| } catch (e) { | ||
| console.log(`Error closing group ${jid}:`, e.message) | ||
| } | ||
| } | ||
| // Open group if time is outside range | ||
| else if (!shouldBeClosed && isClosed) { | ||
| try { | ||
| await conn.groupSettingUpdate(jid, 'not_announcement') | ||
| groupSet.autoclose.isClosed = false | ||
| conn.reply(jid, Func.texted('bold', `🚩 Group status changed OPEN due to autoclose set ${start}-${end}`)) | ||
| } catch (e) { | ||
| console.log(`Error opening group ${jid}:`, e.message) | ||
| } | ||
| } |
There was a problem hiding this comment.
When manually opening or closing a group via the 'group' command (plugins/admin/restrict.js) or 'gcopt' command (plugins/owner/gc.js), the isClosed flag in the autoclose configuration is not updated. This can lead to state inconsistency where the scheduler may immediately re-close a manually opened group or vice versa. Consider adding logic to update groupSet.autoclose.isClosed when manual group status changes occur, or handle this scenario in the scheduler to check actual group status before making changes.
| try { | ||
| await conn.groupSettingUpdate(jid, 'announcement') | ||
| groupSet.autoclose.isClosed = true | ||
| conn.reply(jid, Func.texted('bold', `🚩 Group status changed CLOSED due to autoclose set ${start}-${end}`)) |
There was a problem hiding this comment.
The error message states 'Group status changed CLOSED' but the past tense and capitalization are inconsistent with the opening message. For consistency with the corresponding open message (line 67), consider changing to 'Group status changed to CLOSED' or simply 'Group has been closed automatically'.
| conn.reply(jid, Func.texted('bold', `🚩 Group status changed CLOSED due to autoclose set ${start}-${end}`)) | |
| conn.reply(jid, Func.texted('bold', `🚩 Group status changed to CLOSED due to autoclose set ${start}-${end}`)) |
| try { | ||
| await conn.groupSettingUpdate(jid, 'not_announcement') | ||
| groupSet.autoclose.isClosed = false | ||
| conn.reply(jid, Func.texted('bold', `🚩 Group status changed OPEN due to autoclose set ${start}-${end}`)) |
There was a problem hiding this comment.
The error message states 'Group status changed OPEN' but would be clearer and more consistent with the closing message if it said 'Group status changed to OPEN' or 'Group has been opened automatically'.
| } | ||
|
|
||
| if (args[0].toLowerCase() === 'off') { | ||
| groupSet.autoclose.active = false |
There was a problem hiding this comment.
When disabling autoclose with 'off', consider resetting the isClosed flag to false as well. If a group is currently in the closed state due to autoclose and an admin disables the feature, leaving isClosed as true could cause confusion about the intended state. It would be clearer to set isClosed to false when the feature is disabled.
| groupSet.autoclose.active = false | |
| groupSet.autoclose.active = false | |
| groupSet.autoclose.isClosed = false |
| if (type === 'autoclose' && !setting.autoclose) { | ||
| setting.autoclose = { | ||
| active: false, | ||
| start: null, | ||
| end: null, | ||
| isClosed: false | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
The initialization logic for autoclose in moderation.js duplicates the object structure already defined in lib/system/models.js (lines 41-46). If the structure needs to change in the future, it would need to be updated in multiple places. Consider extracting this to a shared function or constant, or simply rely on the model defaults being applied during database initialization.
| if (type === 'autoclose' && !setting.autoclose) { | |
| setting.autoclose = { | |
| active: false, | |
| start: null, | |
| end: null, | |
| isClosed: false | |
| } | |
| } | |
| if (!groupSet.autoclose) { | ||
| groupSet.autoclose = { | ||
| active: false, | ||
| start: null, | ||
| end: null, | ||
| isClosed: false | ||
| } | ||
| } |
There was a problem hiding this comment.
Similar to moderation.js, this initialization duplicates the autoclose object structure from lib/system/models.js. Consider using a shared constant or helper function to maintain consistency and reduce duplication.
| const endTotalMin = endHour * 60 + endMin | ||
|
|
||
| if (startTotalMin >= endTotalMin) { | ||
| return conn.reply(m.chat, `🚩 *Invalid time range!*\n\nStart time must be before end time.\n\n*Example* : ${Func.texted('code', '.setautoclose 14:30-15:00')}`, m) |
There was a problem hiding this comment.
The time range validation does not handle cross-midnight scenarios. For example, if a group admin wants to close from 23:00 to 01:00 (close at 11 PM and open at 1 AM), this validation would reject it since startTotalMin (1380) is greater than endTotalMin (60). Consider supporting cross-midnight time ranges or adding a clear error message explaining this limitation.
| return conn.reply(m.chat, `🚩 *Invalid time range!*\n\nStart time must be before end time.\n\n*Example* : ${Func.texted('code', '.setautoclose 14:30-15:00')}`, m) | |
| return conn.reply(m.chat, `🚩 *Invalid time range!*\n\nStart time must be earlier than end time *on the same day*.\nCross-midnight ranges like 23:00-01:00 are currently not supported.\n\n*Example* : ${Func.texted('code', '.setautoclose 14:30-15:00')}`, m) |
| cron.schedule('* * * * *', async () => { | ||
| try { | ||
| if (!global.db?.groups) return | ||
|
|
||
| const currentTime = getCurrentTime() | ||
|
|
||
| for (const [jid, groupSet] of Object.entries(global.db.groups)) { | ||
| if (!groupSet.autoclose?.active || !groupSet.autoclose.start || !groupSet.autoclose.end) continue | ||
|
|
||
| const { start, end, isClosed } = groupSet.autoclose | ||
| const shouldBeClosed = isTimeInRange(currentTime, start, end) | ||
|
|
||
| // Close group if time is within range | ||
| if (shouldBeClosed && !isClosed) { | ||
| try { | ||
| await conn.groupSettingUpdate(jid, 'announcement') | ||
| groupSet.autoclose.isClosed = true | ||
| conn.reply(jid, Func.texted('bold', `🚩 Group status changed CLOSED due to autoclose set ${start}-${end}`)) | ||
| } catch (e) { | ||
| console.log(`Error closing group ${jid}:`, e.message) | ||
| } | ||
| } | ||
| // Open group if time is outside range | ||
| else if (!shouldBeClosed && isClosed) { | ||
| try { | ||
| await conn.groupSettingUpdate(jid, 'not_announcement') | ||
| groupSet.autoclose.isClosed = false | ||
| conn.reply(jid, Func.texted('bold', `🚩 Group status changed OPEN due to autoclose set ${start}-${end}`)) | ||
| } catch (e) { | ||
| console.log(`Error opening group ${jid}:`, e.message) | ||
| } | ||
| } | ||
| } | ||
| } catch (e) { | ||
| console.log('Error in auto_close scheduler:', e.message) | ||
| } | ||
| }, { | ||
| scheduled: true, | ||
| timezone: process.env.TZ || 'Asia/Jakarta' | ||
| }) |
There was a problem hiding this comment.
The cron job runs every minute for all groups with autoclose enabled. If there are many groups (e.g., hundreds), this could become a performance bottleneck. Consider implementing a more efficient scheduling approach, such as using separate timers for each group or batching group status changes. Additionally, the current approach may cause all groups to be processed simultaneously at the top of each minute, creating a spike in API calls to WhatsApp.
Add Auto Group Open/Close Feature
Summary
This PR adds an auto group open/close feature that allows group admins to schedule when a group should be opened or closed automatically.
This addresses the request in issue #19
Changes
Screenshot