diff --git a/cmd/common.go b/cmd/common.go
index 05059b4..cee8c16 100644
--- a/cmd/common.go
+++ b/cmd/common.go
@@ -18,9 +18,14 @@ package cmd
 
 import (
 	"fmt"
+	"sync"
+	"time"
 
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
+	"go.uploadedlobster.com/scotty/internal/backends"
+	"go.uploadedlobster.com/scotty/internal/models"
+	"go.uploadedlobster.com/scotty/internal/storage"
 )
 
 func getConfigFromFlag(cmd *cobra.Command, flagName string) (string, *viper.Viper) {
@@ -43,3 +48,114 @@ func getInt64FromFlag(cmd *cobra.Command, flagName string) (result int64) {
 	}
 	return
 }
+
+type backendInfo[T models.Backend, R models.ListensResult | models.LovesResult] struct {
+	configName string
+	backend    T
+}
+
+type exportBackendInfo[T models.Backend, R models.ListensResult | models.LovesResult] struct {
+	backendInfo[T, R]
+	processor backends.ExportProcessor[R]
+}
+
+type importBackendInfo[T models.Backend, R models.ListensResult | models.LovesResult] struct {
+	backendInfo[T, R]
+	processor backends.ImportProcessor[R]
+}
+
+func resolveBackends[E models.Backend, I models.ImportBackend, R models.ListensResult | models.LovesResult](cmd *cobra.Command) (*exportBackendInfo[E, R], *importBackendInfo[I, R], error) {
+	sourceName, sourceConfig := getConfigFromFlag(cmd, "from")
+	targetName, targetConfig := getConfigFromFlag(cmd, "to")
+	// Initialize backends
+	exportBackend, err := backends.ResolveBackend[E](sourceConfig)
+	if err != nil {
+		return nil, nil, err
+	}
+	importBackend, err := backends.ResolveBackend[I](targetConfig)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	exportInfo := exportBackendInfo[E, R]{
+		backendInfo: backendInfo[E, R]{
+			configName: sourceName,
+			backend:    exportBackend,
+		},
+	}
+
+	importInfo := importBackendInfo[I, R]{
+		backendInfo: backendInfo[I, R]{
+			configName: targetName,
+			backend:    importBackend,
+		},
+	}
+
+	return &exportInfo, &importInfo, nil
+}
+
+func cmdExportImport[E models.Backend, I models.ImportBackend, R models.ListensResult | models.LovesResult](cmd *cobra.Command, entity string, exp *exportBackendInfo[E, R], imp *importBackendInfo[I, R]) {
+	sourceName := exp.configName
+	targetName := imp.configName
+	fmt.Printf("Transferring %s from %s to %s...\n", entity, sourceName, targetName)
+
+	// Setup database
+	db, err := storage.New(viper.GetString("database"))
+	cobra.CheckErr(err)
+
+	// Authenticate backends, if needed
+	config := viper.GetViper()
+	_, err = backends.Authenticate(sourceName, exp.backend, db, config)
+	cobra.CheckErr(err)
+
+	_, err = backends.Authenticate(targetName, imp.backend, db, config)
+	cobra.CheckErr(err)
+
+	// Read timestamp
+	timestamp := time.Unix(getInt64FromFlag(cmd, "timestamp"), 0)
+	if timestamp == time.Unix(0, 0) {
+		timestamp, err = db.GetImportTimestamp(sourceName, targetName, entity)
+		cobra.CheckErr(err)
+	}
+	fmt.Printf("From timestamp: %v (%v)\n", timestamp, timestamp.Unix())
+
+	// Prepare progress bars
+	exportProgress := make(chan models.Progress)
+	importProgress := make(chan models.Progress)
+	var wg sync.WaitGroup
+	progress := progressBar(&wg, exportProgress, importProgress)
+
+	// Export from source
+	exportChan := make(chan R, 1000)
+	go exp.processor.Process(timestamp, exportChan, exportProgress)
+
+	// Import into target
+	resultChan := make(chan models.ImportResult)
+	go imp.processor.Process(exportChan, resultChan, importProgress)
+	result := <-resultChan
+	close(exportProgress)
+	wg.Wait()
+	progress.Wait()
+	if result.Error != nil {
+		fmt.Printf("Import failed, last reported timestamp was %v (%v)\n", result.LastTimestamp, result.LastTimestamp.Unix())
+		cobra.CheckErr(result.Error)
+	}
+	fmt.Printf("Imported %v of %v %s into %v.\n",
+		result.ImportCount, result.TotalCount, entity, targetName)
+
+	// Update timestamp
+	if result.LastTimestamp.Unix() < timestamp.Unix() {
+		result.LastTimestamp = timestamp
+	}
+	fmt.Printf("Latest timestamp: %v (%v)\n", result.LastTimestamp, result.LastTimestamp.Unix())
+	err = db.SetImportTimestamp(sourceName, targetName, entity, result.LastTimestamp)
+	cobra.CheckErr(err)
+
+	// Print errors
+	if len(result.ImportErrors) > 0 {
+		fmt.Printf("\nDuring the import the following errors occurred:\n")
+		for _, err := range result.ImportErrors {
+			fmt.Printf("Error: %v\n", err)
+		}
+	}
+}
diff --git a/cmd/listens.go b/cmd/listens.go
index 9093dbc..1d52dc5 100644
--- a/cmd/listens.go
+++ b/cmd/listens.go
@@ -17,15 +17,9 @@ Scotty. If not, see <https://www.gnu.org/licenses/>.
 package cmd
 
 import (
-	"fmt"
-	"sync"
-	"time"
-
 	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
 	"go.uploadedlobster.com/scotty/internal/backends"
 	"go.uploadedlobster.com/scotty/internal/models"
-	"go.uploadedlobster.com/scotty/internal/storage"
 )
 
 // listensCmd represents the listens command
@@ -34,77 +28,15 @@ var listensCmd = &cobra.Command{
 	Short: "Transfer listens between two services",
 	Long:  `Transfers listens between two configured services.`,
 	Run: func(cmd *cobra.Command, args []string) {
-		sourceName, sourceConfig := getConfigFromFlag(cmd, "from")
-		targetName, targetConfig := getConfigFromFlag(cmd, "to")
-		fmt.Printf("Transferring listens from %s to %s...\n", sourceName, targetName)
-
-		// Setup database
-		db, err := storage.New(viper.GetString("database"))
+		exp, imp, err := resolveBackends[models.ListensExport, models.ListensImport, models.ListensResult](cmd)
 		cobra.CheckErr(err)
-
-		// Initialize backends
-		exportBackend, err := backends.ResolveBackend[models.ListensExport](sourceConfig)
-		cobra.CheckErr(err)
-		importBackend, err := backends.ResolveBackend[models.ListensImport](targetConfig)
-		cobra.CheckErr(err)
-
-		// Authenticate backends, if needed
-		_, err = backends.Authenticate(sourceName, exportBackend, db, viper.GetViper())
-		cobra.CheckErr(err)
-
-		_, err = backends.Authenticate(targetName, importBackend, db, viper.GetViper())
-		cobra.CheckErr(err)
-
-		// Read timestamp
-		timestamp := time.Unix(getInt64FromFlag(cmd, "timestamp"), 0)
-		if timestamp == time.Unix(0, 0) {
-			timestamp, err = db.GetImportTimestamp(sourceName, targetName, "listens")
-			cobra.CheckErr(err)
+		exp.processor = backends.ListensExportProcessor{
+			Backend: exp.backend,
 		}
-		fmt.Printf("From timestamp: %v (%v)\n", timestamp, timestamp.Unix())
-
-		// Prepare progress bars
-		exportProgress := make(chan models.Progress)
-		importProgress := make(chan models.Progress)
-		var wg sync.WaitGroup
-		progress := progressBar(&wg, exportProgress, importProgress)
-
-		// Export from source
-		exportChan := make(chan models.ListensResult, 1000)
-		go exportBackend.ExportListens(timestamp, exportChan, exportProgress)
-
-		// Import into target
-		resultChan := make(chan models.ImportResult)
-		var processor = backends.ListensImportProcessor{
-			Backend: importBackend,
-		}
-		go processor.Process(exportChan, resultChan, importProgress)
-		result := <-resultChan
-		close(exportProgress)
-		wg.Wait()
-		progress.Wait()
-		if result.Error != nil {
-			fmt.Printf("Import failed, last reported timestamp was %v (%v)\n", result.LastTimestamp, result.LastTimestamp.Unix())
-			cobra.CheckErr(result.Error)
-		}
-		fmt.Printf("Imported %v of %v listens into %v.\n",
-			result.ImportCount, result.TotalCount, targetName)
-
-		// Update timestamp
-		if result.LastTimestamp.Unix() < timestamp.Unix() {
-			result.LastTimestamp = timestamp
-		}
-		fmt.Printf("Latest timestamp: %v (%v)\n", result.LastTimestamp, result.LastTimestamp.Unix())
-		err = db.SetImportTimestamp(sourceName, targetName, "listens", result.LastTimestamp)
-		cobra.CheckErr(err)
-
-		// Print errors
-		if len(result.ImportErrors) > 0 {
-			fmt.Printf("\nDuring the import the following errors occurred:\n")
-			for _, err := range result.ImportErrors {
-				fmt.Printf("Error: %v\n", err)
-			}
+		imp.processor = backends.ListensImportProcessor{
+			Backend: imp.backend,
 		}
+		cmdExportImport(cmd, "listens", exp, imp)
 	},
 }
 
diff --git a/cmd/loves.go b/cmd/loves.go
index 623b477..ea257a0 100644
--- a/cmd/loves.go
+++ b/cmd/loves.go
@@ -17,15 +17,9 @@ Scotty. If not, see <https://www.gnu.org/licenses/>.
 package cmd
 
 import (
-	"fmt"
-	"sync"
-	"time"
-
 	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
 	"go.uploadedlobster.com/scotty/internal/backends"
 	"go.uploadedlobster.com/scotty/internal/models"
-	"go.uploadedlobster.com/scotty/internal/storage"
 )
 
 // lovesCmd represents the loves command
@@ -34,77 +28,15 @@ var lovesCmd = &cobra.Command{
 	Short: "Transfer loves between two services",
 	Long:  `Transfers loves between two configured services.`,
 	Run: func(cmd *cobra.Command, args []string) {
-		sourceName, sourceConfig := getConfigFromFlag(cmd, "from")
-		targetName, targetConfig := getConfigFromFlag(cmd, "to")
-		fmt.Printf("Transferring loves from %s to %s...\n", sourceName, targetName)
-
-		// Setup database
-		db, err := storage.New(viper.GetString("database"))
+		exp, imp, err := resolveBackends[models.LovesExport, models.LovesImport, models.LovesResult](cmd)
 		cobra.CheckErr(err)
-
-		// Initialize backends
-		exportBackend, err := backends.ResolveBackend[models.LovesExport](sourceConfig)
-		cobra.CheckErr(err)
-		importBackend, err := backends.ResolveBackend[models.LovesImport](targetConfig)
-		cobra.CheckErr(err)
-
-		// Authenticate backends, if needed
-		_, err = backends.Authenticate(sourceName, exportBackend, db, viper.GetViper())
-		cobra.CheckErr(err)
-
-		_, err = backends.Authenticate(targetName, importBackend, db, viper.GetViper())
-		cobra.CheckErr(err)
-
-		// Read timestamp
-		timestamp := time.Unix(getInt64FromFlag(cmd, "timestamp"), 0)
-		if timestamp == time.Unix(0, 0) {
-			timestamp, err = db.GetImportTimestamp(sourceName, targetName, "loves")
-			cobra.CheckErr(err)
+		exp.processor = backends.LovesExportProcessor{
+			Backend: exp.backend,
 		}
-		fmt.Printf("From timestamp: %v (%v)\n", timestamp, timestamp.Unix())
-
-		// Prepare progress bars
-		exportProgress := make(chan models.Progress)
-		importProgress := make(chan models.Progress)
-		var wg sync.WaitGroup
-		progress := progressBar(&wg, exportProgress, importProgress)
-
-		// Export from source
-		exportChan := make(chan models.LovesResult, 1000)
-		go exportBackend.ExportLoves(timestamp, exportChan, exportProgress)
-
-		// Import into target
-		resultChan := make(chan models.ImportResult)
-		var processor = backends.LovesImportProcessor{
-			Backend: importBackend,
-		}
-		go processor.Process(exportChan, resultChan, importProgress)
-		result := <-resultChan
-		close(exportProgress)
-		wg.Wait()
-		progress.Wait()
-		if result.Error != nil {
-			fmt.Printf("Import failed, last reported timestamp was %v (%v)\n", result.LastTimestamp, result.LastTimestamp.Unix())
-			cobra.CheckErr(result.Error)
-		}
-		fmt.Printf("Imported %v of %v loves into %v.\n",
-			result.ImportCount, result.TotalCount, targetName)
-
-		// Update timestamp
-		if result.LastTimestamp.Unix() < timestamp.Unix() {
-			result.LastTimestamp = timestamp
-		}
-		fmt.Printf("Latest timestamp: %v (%v)\n", result.LastTimestamp, result.LastTimestamp.Unix())
-		err = db.SetImportTimestamp(sourceName, targetName, "loves", result.LastTimestamp)
-		cobra.CheckErr(err)
-
-		// Print errors
-		if len(result.ImportErrors) > 0 {
-			fmt.Printf("\nDuring the import the following errors occurred:\n")
-			for _, err := range result.ImportErrors {
-				fmt.Printf("Error: %v\n", err)
-			}
+		imp.processor = backends.LovesImportProcessor{
+			Backend: imp.backend,
 		}
+		cmdExportImport(cmd, "loves", exp, imp)
 	},
 }
 
diff --git a/internal/backends/export.go b/internal/backends/export.go
new file mode 100644
index 0000000..9bdb8a1
--- /dev/null
+++ b/internal/backends/export.go
@@ -0,0 +1,51 @@
+/*
+Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
+
+Scotty is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later version.
+
+Scotty is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+Scotty. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+package backends
+
+import (
+	"time"
+
+	"go.uploadedlobster.com/scotty/internal/models"
+)
+
+type ExportProcessor[T models.ListensResult | models.LovesResult] interface {
+	ExportBackend() models.Backend
+	Process(oldestTimestamp time.Time, results chan T, progress chan models.Progress)
+}
+
+type ListensExportProcessor struct {
+	Backend models.ListensExport
+}
+
+func (p ListensExportProcessor) ExportBackend() models.Backend {
+	return p.Backend
+}
+
+func (p ListensExportProcessor) Process(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) {
+	p.Backend.ExportListens(oldestTimestamp, results, progress)
+}
+
+type LovesExportProcessor struct {
+	Backend models.LovesExport
+}
+
+func (p LovesExportProcessor) ExportBackend() models.Backend {
+	return p.Backend
+}
+
+func (p LovesExportProcessor) Process(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) {
+	p.Backend.ExportLoves(oldestTimestamp, results, progress)
+}
diff --git a/internal/backends/import.go b/internal/backends/import.go
index cd47960..6365cbb 100644
--- a/internal/backends/import.go
+++ b/internal/backends/import.go
@@ -17,7 +17,9 @@ Scotty. If not, see <https://www.gnu.org/licenses/>.
 
 package backends
 
-import "go.uploadedlobster.com/scotty/internal/models"
+import (
+	"go.uploadedlobster.com/scotty/internal/models"
+)
 
 type ImportProcessor[T models.ListensResult | models.LovesResult] interface {
 	ImportBackend() models.ImportBackend