@@ -280,45 +280,91 @@ def get_forwarders(self):
280280 forwarders = []
281281
282282 if isinstance (response , dict ):
283- # Look for any sign of forwarders in the response
284- for key , value in response .items ():
285- print (f"Processing key: '{ key } ' with value: '{ value } '" )
286-
287- if key .startswith ('error' ):
288- print (f"Found error: { value } " )
289- continue
290-
291- # Various possible formats
292- if '@' in str (key ):
293- forwarders .append ({
294- 'address' : key ,
295- 'destination' : str (value )
296- })
297- elif 'forward' in str (key ).lower ():
298- print (f"Found forward-related key: { key } = { value } " )
299- elif value and '=' in str (value ):
300- parts = str (value ).split ('=' , 1 )
283+ # DirectAdmin commonly uses these formats for forwarders:
284+
285+ # Format 1: select0, select1, etc. (common for lists)
286+ select_keys = [k for k in response .keys () if k .startswith ('select' )]
287+ if select_keys :
288+ print (f"Found select keys: { select_keys } " )
289+ for key in select_keys :
290+ value = response [key ]
291+ if '=' in str (value ):
292+ # Format: alias=destination
293+ parts = str (value ).split ('=' , 1 )
294+ if len (parts ) == 2 :
295+ forwarders .append ({
296+ 'address' : f"{ parts [0 ]} @{ self .domain } " ,
297+ 'destination' : parts [1 ]
298+ })
299+ elif '@' in str (value ):
300+ # Might be just the email address
301+ # Check if there's a corresponding destination key
302+ dest_key = key .replace ('select' , 'destination' )
303+ if dest_key in response :
304+ forwarders .append ({
305+ 'address' : value ,
306+ 'destination' : response [dest_key ]
307+ })
308+
309+ # Format 2: list[] array
310+ elif 'list' in response and isinstance (response ['list' ], list ):
311+ print ("Found list array" )
312+ for item in response ['list' ]:
313+ if '=' in str (item ):
314+ parts = str (item ).split ('=' , 1 )
315+ if len (parts ) == 2 :
316+ forwarders .append ({
317+ 'address' : f"{ parts [0 ]} @{ self .domain } " if '@' not in parts [0 ] else parts [0 ],
318+ 'destination' : parts [1 ]
319+ })
320+
321+ # Format 3: Direct email keys
322+ else :
323+ # Look for email addresses as keys
324+ for key , value in response .items ():
325+ if key .startswith ('error' ) or key == 'domain' :
326+ continue
327+
328+ # If key contains @ it's likely an email
329+ if '@' in str (key ):
330+ forwarders .append ({
331+ 'address' : key ,
332+ 'destination' : str (value )
333+ })
334+ # If key is a username and value contains destination
335+ elif value and '=' not in str (key ):
336+ # Could be username as key, destination as value
337+ if '@' in str (value ):
338+ forwarders .append ({
339+ 'address' : f"{ key } @{ self .domain } " ,
340+ 'destination' : str (value )
341+ })
342+ # Or could be a forward entry
343+ elif '=' in str (value ):
344+ parts = str (value ).split ('=' , 1 )
345+ if len (parts ) == 2 :
346+ forwarders .append ({
347+ 'address' : f"{ parts [0 ]} @{ self .domain } " if '@' not in parts [0 ] else parts [0 ],
348+ 'destination' : parts [1 ]
349+ })
350+
351+ elif isinstance (response , str ):
352+ print ("Response is string, parsing..." )
353+ lines = response .strip ().split ('\n ' )
354+ for line in lines :
355+ line = line .strip ()
356+ if '=' in line :
357+ parts = line .split ('=' , 1 )
301358 if len (parts ) == 2 :
302359 forwarders .append ({
303- 'address' : parts [0 ],
360+ 'address' : f" { parts [ 0 ] } @ { self . domain } " if '@' not in parts [ 0 ] else parts [0 ],
304361 'destination' : parts [1 ]
305362 })
306363
307- elif isinstance (response , str ):
308- print ("Response is string, checking for forwarders..." )
309- # Maybe it's a different format
310- if '@' in response :
311- lines = response .strip ().split ('\n ' )
312- for line in lines :
313- if '=' in line and '@' in line :
314- parts = line .split ('=' , 1 )
315- if len (parts ) == 2 :
316- forwarders .append ({
317- 'address' : parts [0 ].strip (),
318- 'destination' : parts [1 ].strip ()
319- })
364+ print (f"\n Parsed { len (forwarders )} forwarders:" )
365+ for f in forwarders :
366+ print (f" { f ['address' ]} -> { f ['destination' ]} " )
320367
321- print (f"\n Parsed { len (forwarders )} forwarders" )
322368 return forwarders
323369
324370 except Exception as e :
@@ -330,41 +376,86 @@ def get_forwarders(self):
330376 def create_forwarder (self , address , destination ):
331377 """Create an email forwarder"""
332378 try :
333- # Ensure full email addresses
334- if '@' not in address :
335- address = f"{ address } @{ self .domain } "
379+ # Ensure we have just the username part
380+ if '@' in address :
381+ username = address .split ('@' )[0 ]
382+ else :
383+ username = address
384+
385+ # Various parameter formats DirectAdmin might expect
386+ param_sets = [
387+ # Format 1: Standard 【1】
388+ {
389+ 'domain' : self .domain ,
390+ 'action' : 'create' ,
391+ 'user' : username ,
392+ 'email' : destination
393+ },
394+ # Format 2: With select0
395+ {
396+ 'domain' : self .domain ,
397+ 'action' : 'create' ,
398+ 'select0' : username ,
399+ 'email' : destination
400+ },
401+ # Format 3: Forward specific
402+ {
403+ 'domain' : self .domain ,
404+ 'action' : 'create' ,
405+ 'forward' : f"{ username } ={ destination } "
406+ }
407+ ]
336408
337- # Extract username from address
338- username = address .split ('@' )[0 ]
409+ print (f"\n === Creating Forwarder ===" )
410+ print (f"Username: { username } " )
411+ print (f"Domain: { self .domain } " )
412+ print (f"Destination: { destination } " )
339413
340- endpoint = '/CMD_API_EMAIL_FORWARDERS'
341- data = {
342- 'domain' : self .domain ,
343- 'action' : 'create' ,
344- 'user' : username ,
345- 'email' : destination
346- }
414+ response = None
415+ for i , data in enumerate (param_sets ):
416+ print (f"\n Trying parameter set { i + 1 } : { data } " )
347417
348- print ( f" \n === Creating Forwarder ===" )
349- print ( f"Address: { address } -> { destination } " )
418+ endpoint = '/CMD_API_EMAIL_FORWARDERS'
419+ response = self . _make_request ( endpoint , data , method = 'POST' )
350420
351- response = self ._make_request (endpoint , data )
421+ if response :
422+ print (f"Got response: { response } " )
423+
424+ if isinstance (response , dict ):
425+ # Check for errors
426+ if 'error' in response :
427+ error_msg = response .get ('error' , 'Unknown error' )
428+ error_code = response .get ('error_code' , '' )
429+ details = response .get ('details' , '' )
430+ text = response .get ('text' , '' )
431+
432+ # API error 1 often means permission or format issues 【2】
433+ if error_msg == '1' or error_code == '1' :
434+ print (f"API Error 1 detected. Details: { details } , Text: { text } " )
435+ # Try next parameter set
436+ continue
437+ else :
438+ return False , f"Error: { error_msg } { details } { text } " .strip ()
352439
353- if response :
354- # Check for success
355- if isinstance (response , dict ):
356- if 'error' in response :
357- return False , response .get ('error' , 'Unknown error' )
358- elif 'success' in response or 'created' in response :
359- return True , f"Forwarder { address } → { destination } created successfully"
440+ # Check for success indicators
441+ if any (key in response for key in ['success' , 'created' , 'added' ]):
442+ return True , f"Forwarder { username } @{ self .domain } → { destination } created"
360443
361- # If no error, assume success
362- return True , f"Forwarder { address } → { destination } created"
444+ # If no error and no explicit success, might still be OK
445+ if not any (key .startswith ('error' ) for key in response .keys ()):
446+ return True , f"Forwarder { username } @{ self .domain } → { destination } created"
363447
364- return False , "Failed to create forwarder"
448+ elif isinstance (response , str ):
449+ if 'error' not in response .lower ():
450+ return True , f"Forwarder { username } @{ self .domain } → { destination } created"
451+
452+ # If all parameter sets failed
453+ return False , "Failed to create forwarder. Check DirectAdmin logs for details."
365454
366455 except Exception as e :
367456 print (f"Error creating forwarder: { e } " )
457+ import traceback
458+ traceback .print_exc ()
368459 return False , str (e )
369460
370461 def delete_forwarder (self , address ):
0 commit comments