Please eval short Ruby script to suspend inactive users

This is my first custom Ruby script ever. :slight_smile: In almost 50 years of coding with over 20 languages and dialects, I’ve never needed (or wanted :face_with_open_eyes_and_hand_over_mouth:) to write in Ruby, but OK, you got me … please be gentle. :pray:

My goal is to identify user accounts that have not been authenticated by email links, and suspend the accounts so that I can check them before deletion. During initial setup I had settings in various stages of development. The 2FA emails didn’t go out for some registrations, and there were a lot of bogus registrations that I’d now like to filter out.

Version 0.0.1

rails c

# First ensure user TLs conform to latest definitions
User.all.find_each do |user|
  Promotion.recalculate(user)
end
Group.ensure_consistency!

# Prep for loop
logger = StaffActionLogger.new(User.where("id = 'admin'"))
# temporary date, users will be removed before this date
suspend_till = DateTime.new(2028, 12, 31)
suspend_at = DateTime.now
reason = 'Inactive'

# Identify potentially dead accounts
User.where("views = 0 OR approved = FALSE OR last_seen_at IS NULL")
    .where("id > 0")
    .find_each do |user|
  user.suspended_till = suspend_till
  user.suspended_at = suspend_at
  user.change_trust_level!(TrustLevel[0])
  # save the user, log the action, and ... do whatever is done for this trigger
  user.save!
  logger.log_user_suspend(user, reason)
  DiscourseEvent.trigger(:user_suspended, user: user)
  # avoid abusing the mail server
  sleep(10)
end

By triggering the event, I’d like emails to go out to the users - there will be a bunch of bounces, and there might be a few people who come back to authenticate. After a week I’ll select all accounts that are still suspended and delete them.

Questions:

  1. In general, will this do what is intended?
  2. Is there a better approach?
  3. Is the logging redundant with the event trigger?
  4. Can we do this with JavaScript?
  5. Should I post this to another category?
  6. Any other words of advice?

Thank you!!

1 Like

Hmm, as I look at that I think I should be silencing an account rather than suspending. If the account is suspended, the user can’t login to claim the account.

Here is a revision …

Version 0.0.2
silence_reason = 'Inactive'
User.where("views = 0 OR approved = FALSE OR last_seen_at IS NULL")
    .where("id > 0") # avoid system users
    .find_each do |user|
  user.silence(reason: silence_reason)
  user.change_trust_level!(TrustLevel[0])
  user.save!
  logger.log_user_silence(user, silence_reason)
  DiscourseEvent.trigger(:user_silenced, user: user)
  sleep(5)
end

Yes, I will run a backup before doing any of this.
Yes, I know some existing TLs will get shuffled around and I’ll need to fix them.

In addition, to or rather than silencing, should I set Approved=false or Active=false? I believe that will force the user to click the email link rather than manually doing a login, which serves the purpose of validating the email address.

This is all related to my recent thread: notes-on-silencing-or-deleting-users

[EDIT]
I also have “purge unactivated users grace period days” set to 7.
Does a silence or suspend reset this? If so, if peeps don’t respond to an account action within 7 days, I have no problem with purging them.

Finally (yes, really) I also have “clean up inactive users after days” set to 365. I can set that down to 60 while the forum is still opening up and just let existing accounts fall off the list. Then increase it back to 365. Is that a reasonable approach for automated account pruning on a new environment?

1 Like

Why isn’t that enough to solve your problem?

Shouldn’t those be AND rather than OR?

Discourse enforces them to validate addresses, so I’m not really clear what problem you’re solving. You say this

It seems pretty improbable that you’re going to find users who wanted to log in but couldn’t before, but you may know something I don’t about your setup.

And discourse won’t send emails to addresses that are not validated, so I don’t think this is going to send any emails.

I always do these things on a few accounts but hand to see what’s going to happen.

2 Likes

Thanks for your interest, @pfaffman !

There are user records with no last_seen_at, created months ago, approved=False, active=False, and they are not being purged.
There are user records with last_seen_at > 7 days (months old) and 0 views, and they are not being purged.

Whatever the criteria being used with that grace period flag, it is not selecting these records.
Can someone post the exact query that is being used there so that I can understand what other factors are involved there?

No, looking at the DB directly there are user records that fit each criterion but not all. It seems the users table in the database is inconsistent. There are records with non-zero topics_entered or posts_read_count, but with views=0. There are records with topics_entered=0 and posts_read_count=0, but views is non-zero.

The key point to remember is that as this site was being developed the settings weren’t optimal and humans and bots were registering. The OR clause seems to get all of these. Now that the site is stablized with (I hope) sane settings, I don’t expect new registrations to result in the same anomalies.

I intend to run the script many times with different criteria. I will query records first, outside of the environment, and then run the silence script to target only the records I really want. In a couple weeks I’ll do a final run, just select silenced records (anyone who actually came back will get the flag removed), and purge all of them:

UserDestroyer.new(admin_user).destroy(user, reassign_to: archive_user)

My overall question is whether the v0.0.2 script, with its approach to selection, logging, and silencing, is correct for a Discourse system. I don’t know if there is something else to be done in a loop like this. I’ve never created and run my own script so this is request to check me for noobish knucklehead mistakes.

Thanks!!

1 Like