2929REASON_ACTIONS = ['addremovalreason' ]
3030DEFAULT_WIKI_ACTIONS = REMOVAL_ACTIONS + REASON_ACTIONS + APPROVAL_ACTIONS
3131
32+ # Valid Reddit modlog actions (hardcoded for validation)
33+ VALID_MODLOG_ACTIONS = [
34+ # Content removal
35+ 'removelink' , 'removecomment' , 'spamlink' , 'spamcomment' ,
36+ # Content approval
37+ 'approvelink' , 'approvecomment' ,
38+ # Moderation reasons and notes
39+ 'addremovalreason' , 'addnote' , 'deletenote' ,
40+ # User actions
41+ 'banuser' , 'unbanuser' , 'muteuser' , 'unmuteuser' , 'invitemoderator' , 'acceptmoderatorinvite' ,
42+ # Post management
43+ 'distinguish' , 'undistinguish' , 'sticky' , 'unsticky' , 'lock' , 'unlock' , 'marknsfw' , 'unmarknsfw' ,
44+ # Wiki actions
45+ 'wikirevise' , 'wikipagelisted' , 'wikipermlevel' , 'wikibanned' , 'wikicontributor' ,
46+ # Settings and rules
47+ 'editsettings' , 'editflair' , 'createrule' , 'editrule' , 'deleterule' , 'reorderrules' ,
48+ # Reports and modmail
49+ 'ignorereports' , 'unignorereports' , 'request_assistance' ,
50+ # Community features
51+ 'community_widgets' , 'community_welcome_page' , 'edit_post_requirements' , 'edit_comment_requirements' ,
52+ # Saved responses
53+ 'edit_saved_response' ,
54+ # Collections
55+ 'addtocollection' , 'removefromcollection'
56+ ]
57+
3258# Configuration limits and defaults
3359CONFIG_LIMITS = {
3460 'retention_days' : {'min' : 1 , 'max' : 365 , 'default' : 90 },
@@ -106,6 +132,23 @@ def validate_config_value(key, value, config_limits):
106132
107133 return value
108134
135+ def validate_wiki_actions (wiki_actions ):
136+ """Validate wiki_actions against known Reddit modlog actions"""
137+ if not isinstance (wiki_actions , list ):
138+ raise ValueError ("wiki_actions must be a list" )
139+
140+ if not wiki_actions :
141+ logger .info ("Empty wiki_actions, using defaults" )
142+ return DEFAULT_WIKI_ACTIONS
143+
144+ invalid_actions = [action for action in wiki_actions if action not in VALID_MODLOG_ACTIONS ]
145+
146+ if invalid_actions :
147+ raise ValueError (f"Invalid modlog actions: { invalid_actions } . Valid actions: { sorted (VALID_MODLOG_ACTIONS )} " )
148+
149+ logger .info (f"Validated { len (wiki_actions )} wiki_actions: { wiki_actions } " )
150+ return wiki_actions
151+
109152def apply_config_defaults_and_limits (config ):
110153 """Apply default values and enforce limits on configuration"""
111154 for key , limits in CONFIG_LIMITS .items ():
@@ -119,6 +162,8 @@ def apply_config_defaults_and_limits(config):
119162 if 'wiki_actions' not in config :
120163 config ['wiki_actions' ] = DEFAULT_WIKI_ACTIONS
121164 logger .info ("Using default wiki_actions: removals, removal reasons, and approvals" )
165+ else :
166+ config ['wiki_actions' ] = validate_wiki_actions (config ['wiki_actions' ])
122167
123168 # Validate required fields
124169 required_fields = ['reddit' , 'source_subreddit' ]
@@ -1117,19 +1162,72 @@ def process_modlog_actions(reddit, config: Dict[str, Any]) -> List:
11171162 logger .error (f"Error processing modlog actions: { e } " )
11181163 raise
11191164
1165+ def load_env_config () -> Dict [str , Any ]:
1166+ """Load configuration from environment variables"""
1167+ env_config = {}
1168+
1169+ # Reddit credentials
1170+ reddit_config = {}
1171+ if os .getenv ('REDDIT_CLIENT_ID' ):
1172+ reddit_config ['client_id' ] = os .getenv ('REDDIT_CLIENT_ID' )
1173+ if os .getenv ('REDDIT_CLIENT_SECRET' ):
1174+ reddit_config ['client_secret' ] = os .getenv ('REDDIT_CLIENT_SECRET' )
1175+ if os .getenv ('REDDIT_USERNAME' ):
1176+ reddit_config ['username' ] = os .getenv ('REDDIT_USERNAME' )
1177+ if os .getenv ('REDDIT_PASSWORD' ):
1178+ reddit_config ['password' ] = os .getenv ('REDDIT_PASSWORD' )
1179+
1180+ if reddit_config :
1181+ env_config ['reddit' ] = reddit_config
1182+
1183+ # Application settings
1184+ if os .getenv ('SOURCE_SUBREDDIT' ):
1185+ env_config ['source_subreddit' ] = os .getenv ('SOURCE_SUBREDDIT' )
1186+ if os .getenv ('WIKI_PAGE' ):
1187+ env_config ['wiki_page' ] = os .getenv ('WIKI_PAGE' )
1188+ if os .getenv ('RETENTION_DAYS' ):
1189+ env_config ['retention_days' ] = int (os .getenv ('RETENTION_DAYS' ))
1190+ if os .getenv ('BATCH_SIZE' ):
1191+ env_config ['batch_size' ] = int (os .getenv ('BATCH_SIZE' ))
1192+ if os .getenv ('UPDATE_INTERVAL' ):
1193+ env_config ['update_interval' ] = int (os .getenv ('UPDATE_INTERVAL' ))
1194+ if os .getenv ('ANONYMIZE_MODERATORS' ):
1195+ env_config ['anonymize_moderators' ] = os .getenv ('ANONYMIZE_MODERATORS' ).lower () == 'true'
1196+
1197+ # Wiki actions (comma-separated list)
1198+ if os .getenv ('WIKI_ACTIONS' ):
1199+ try :
1200+ raw_actions = [action .strip () for action in os .getenv ('WIKI_ACTIONS' ).split (',' )]
1201+ env_config ['wiki_actions' ] = validate_wiki_actions (raw_actions )
1202+ except ValueError as e :
1203+ logger .error (f"WIKI_ACTIONS environment variable invalid: { e } " )
1204+ raise
1205+
1206+ # Ignored moderators (comma-separated list)
1207+ if os .getenv ('IGNORED_MODERATORS' ):
1208+ env_config ['ignored_moderators' ] = [mod .strip () for mod in os .getenv ('IGNORED_MODERATORS' ).split (',' )]
1209+
1210+ return env_config
1211+
11201212def load_config (config_path : str , auto_update : bool = True ) -> Dict [str , Any ]:
1121- """Load and validate configuration"""
1213+ """Load and validate configuration from file and environment variables """
11221214 try :
1123- # Load existing config
1215+ # Load existing config from file
11241216 original_config = {}
11251217 config_updated = False
11261218
11271219 try :
11281220 with open (config_path , 'r' ) as f :
11291221 original_config = json .load (f )
11301222 except FileNotFoundError :
1131- logger .error (f"Config file not found: { config_path } " )
1132- raise
1223+ logger .warning (f"Config file not found: { config_path } , using environment variables only" )
1224+ original_config = {}
1225+
1226+ # Override with environment variables
1227+ env_config = load_env_config ()
1228+ if env_config :
1229+ logger .info ("Using environment variable overrides for configuration" )
1230+ original_config .update (env_config )
11331231
11341232 # Store original config for comparison
11351233 config_before = original_config .copy ()
0 commit comments