Skip to content

Commit

Permalink
Add New Settings Page and More (#24)
Browse files Browse the repository at this point in the history
- Add a new settings page to customize features of LASIM
- Store settings page selections in a configuration file in the user's home directory
- Settings options: choose what to upload
- Settings options: Synchronize removals (make LASIM not additive) - Closes #18
- Settings option: Added option for implementing Saved Posts (#8) later
- Fix Rate Limit Calculation (hopefully closes #19) and display/calculate estimated upload time
- Code refactor
- Update README
- Add additional contributors to ABOUT
  • Loading branch information
CMahaff committed Aug 5, 2023
1 parent 5b0f25f commit 1965893
Show file tree
Hide file tree
Showing 10 changed files with 640 additions and 222 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "lasim"
authors = ["Connor Mahaffey"]
version = "0.2.0"
version = "0.2.1"
edition = "2021"
license = "MIT"
publish = false
Expand All @@ -16,6 +16,7 @@ tokio = { version = "~1", features = ["full"] }
serde = { version = "~1.0.164", features = ["derive"] }
serde_json = "~1.0.97"
url = "~2.4.0"
home = "~0.5.5"

[build-dependencies]
slint-build = "~1.0.2"
Expand Down
Binary file modified LASIM.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 34 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,55 @@
## How it works

1. Create an account on the server you want to migrate to.
2. Run LASIM, enter your old account credentials, and hit "Download". Your information is saved to a local file.
2. Run LASIM, enter your old account credentials, and hit "Download". Your information is saved to a local JSON file.
3. In LASIM, hit the Upload tab, then enter your new account credentials and hit "Upload".
4. The local file is used to update your new account's blocked users, blocked communities, followed communities, and *most* profile settings.
5. That's it!
5. **That's it!** Run as many times as you want to keep your accounts in-sync.

## The Settings Tab

There are currently 5 settings you can toggle to alter the default LASIM experience described in *How it works* - they are described in detail below. Note that the state of these settings is written to a settings file in your home directory and restored when LASIM is restarted.

- **Upload Profile Settings** - Whether to take the profile settings of the downloaded profile and apply it to the new profile on upload. These are things like post sort order, NSFW settings, etc.
- **Upload Community Subscriptions** - Whether to add any community subscriptions/follows from the downloaded profile to the new profile on upload.
- **Upload Community Blocks** - Whether to add any community blocks from the downloaded profile to the new profile on upload.
- **Upload User Subscriptions** - Whether to add any user blocks from the downloaded profile to the new profile on upload.
- **Sync Removals** - When enabled, for all *Upload* settings toggled on, also REMOVE any items found in the new profile that are not present in the old profile.
- Put another way (assuming all *Upload* settings are ticked):
- *Sync Removals* ON - the account being uploaded to will be altered to always EXACTLY match the downloaded account.
- *Sync Removals* OFF - the account being uploaded to will always add new follows, new blocks, etc. but will never unfollow or unblock anything
- Why is *Sync Removals* off by default? Because accidents are worse! Take this example:
- You make a brand new account. You intend to add all your subscriptions to it, but by accident, you download the new account and upload to the old account. What happens?
- *Sync Removals* OFF - Basically nothing. At worst, a few of your profile settings (like your default sort) are set back to the Lemmy defaults on your old account.
- *Sync Removals* ON - **Your entire old account is erased leaving you with two "empty" accounts.**
- So please *please* **PLEASE** take care when using *Sync Removals*, and always keep a backup of your account in a separate folder!

## Additional Information
- The downloaded profile file is read in when the user clicks *Upload*
- Therefore do not need to download your profile every time, you can keep a copy and upload as often (or to as many accounts) as you want
- The following profile settings are not modified by LASIM: your avatar image, your banner image, your display name, your email, your bio, your Matrix user, and your 2-Factor token
- All other profile settings will match your old account
- LASIM is otherwise additive - it will add any blocked users, blocked communities, and/or followed communities present in your old account, but not in your new account, but will NOT delete any blocked users, blocked communities, and/or followed communities already present in your new account
- LASIM will automatically detect if your new account already has some of the blocked users, blocked communities, and/or followed communities and will not re-issue those API calls
- LASIM respects the API rate limits set by your instance owner, so some servers may take longer than others
- LASIM is additive by default - it cannot unfollow or unblock anything.
- LASIM has a *Sync Removals* option that makes it NOT additive. Heed the warnings listed in the *Settings Tab* section.
- LASIM will automatically detect if your new account already has some of the blocked users, blocked communities, and/or followed communities and will not re-issue those API calls. This means it is faster on subsequent runs.
- LASIM respects the API rate limits set by your instance owner, so some servers may take longer than others. **Be patient, it has not frozen!**
- LASIM will skip entries that fail to apply - re-run LASIM to try these entries again
- LASIM has to make numerous API calls to migrate everything - be patient
- This should go without saying, but obviously both your new and old accounts are still distinct - LASIM simply makes it easier to move from one to the other

## Limitations
- Versions of LASIM only target specific Lemmy BE API versions, which are currently changing rapidly. See the Version Support table.
- Versions of LASIM only target specific Lemmy BE versions, which are currently changing rapidly. See the Version Support table.
- Download older versions of LASIM that are compatible with older instances as necessary.
- You can find your Lemmy BE Version at the bottom of any page of your Lemmy instance.
- Profile Versions refer to the format of the LASIM profile written when you click "Download".
- As long as the "Profile Version" is the same between LASIM versions, it is possible to use different LASIM versions together to target Lemmy servers running different incompatible API versions.
- Old versions of your LASIM profile are compatible with newer versions of LASIM, but the reverse is not true.
- If your version is not explicitely listed, take the latest LASIM that is available!
- Running multiple copies of LASIM *simultaneously* is not suggested since it can clobber the *Settings* in your home directory.

## Version Support
| LASIM Version | LASIM Profile Version | Supported Lemmy BE API Version(s) |
*If your Lemmy BE version is not explicitely listed below, use the latest LASIM available for download.*

| LASIM Version | LASIM Profile Version | Supported Lemmy BE Version(s) |
| ------------- | --------------------- | --------------------------------- |
| 0.1.\* | 1 | 0.18.1 (rc.9+), 0.18.2 |
| 0.2.\* | 2 | 0.18.3 |
20 changes: 12 additions & 8 deletions src/lemmy/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use reqwest::Response;
use reqwest::Error;
use url::Url;
use crate::profile;
use crate::lemmy::typecast::ToAPI;

pub struct Api {
client: Client,
Expand Down Expand Up @@ -113,12 +114,13 @@ impl Api {

pub async fn block_community(&self,
jwt_token: &str,
community_id: newtypes::CommunityId) -> Result<community::BlockCommunityResponse, Error> {
community_id: newtypes::CommunityId,
block: bool) -> Result<community::BlockCommunityResponse, Error> {

let url = self.instance.join("/api/v3/community/block").unwrap();
let params = community::BlockCommunity {
community_id,
block: true,
block: block,
auth: Sensitive::new(jwt_token.to_string()),
};

Expand All @@ -144,12 +146,13 @@ impl Api {

pub async fn follow_community(&self,
jwt_token: &str,
community_id: newtypes::CommunityId) -> Result<community::CommunityResponse, Error> {
community_id: newtypes::CommunityId,
follow: bool) -> Result<community::CommunityResponse, Error> {

let url = self.instance.join("/api/v3/community/follow").unwrap();
let params = community::FollowCommunity {
community_id,
follow: true,
follow: follow,
auth: Sensitive::new(jwt_token.to_string()),
};

Expand Down Expand Up @@ -205,12 +208,13 @@ impl Api {

pub async fn block_user(&self,
jwt_token: &str,
person_id: newtypes::PersonId) -> Result<person::BlockPersonResponse, Error> {
person_id: newtypes::PersonId,
block: bool) -> Result<person::BlockPersonResponse, Error> {

let url = self.instance.join("/api/v3/user/block").unwrap();
let params = person::BlockPerson {
person_id,
block: true,
block: block,
auth: Sensitive::new(jwt_token.to_string()),
};

Expand Down Expand Up @@ -239,7 +243,7 @@ impl Api {
user_settings_local: profile::ProfileSettings) -> Result<person::LoginResponse, Error> {

let url = self.instance.join("/api/v3/user/save_user_settings").unwrap();
let mut user_settings_api = profile::construct_settings(&user_settings_local);
let mut user_settings_api = ToAPI::construct_settings(&user_settings_local);
user_settings_api.auth = Sensitive::new(jwt_token.to_string());

let response: Response = self.client
Expand All @@ -261,4 +265,4 @@ impl Api {
}
}
}
}
}
103 changes: 103 additions & 0 deletions src/lemmy/typecast.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
use crate::profile::ProfileSettings;
use crate::profile::ProfileConfiguration;

use lemmy_api_common::lemmy_db_schema::newtypes;
use lemmy_api_common::lemmy_db_schema;
use lemmy_api_common::person;
use lemmy_api_common::site;
use lemmy_api_common::sensitive::Sensitive;

pub struct ToAPI {}

impl ToAPI {
pub fn construct_settings(profile_settings: &ProfileSettings) -> person::SaveUserSettings {
return person::SaveUserSettings {
show_nsfw: Some(profile_settings.show_nsfw),
show_scores: Some(profile_settings.show_scores),
theme: Some(profile_settings.theme.clone()),
default_sort_type: Some(Self::cast_sort_type(&profile_settings.default_sort_type)),
default_listing_type: Some(Self::cast_listing_type(&profile_settings.default_listing_type)),
interface_language: Some(profile_settings.interface_language.clone()),
avatar: None, // TODO: Support Avatar migration
banner: None, // TODO: Support Banner migration
display_name: None, // Don't Change
email: None, // Don't Change
bio: None, // Don't Change
matrix_user_id: None, // Don't Change
show_avatars: Some(profile_settings.show_avatars),
send_notifications_to_email: Some(profile_settings.send_notifications_to_email),
bot_account: Some(profile_settings.bot_account),
show_bot_accounts: Some(profile_settings.show_bot_accounts),
show_read_posts: Some(profile_settings.show_read_posts),
show_new_post_notifs: Some(profile_settings.show_new_post_notifs),
discussion_languages: Some(Self::cast_language_array(&profile_settings.discussion_languages)),
generate_totp_2fa: None, // Don't change
auth: Sensitive::from(""), // This will be inserted before the request is sent
open_links_in_new_tab: Some(profile_settings.open_links_in_new_tab),
infinite_scroll_enabled: Some(profile_settings.infinite_scroll_enabled),
};
}

pub fn cast_language_array(original_languages: &Vec<i32>) -> Vec<newtypes::LanguageId> {
let mut new_languages: Vec<newtypes::LanguageId> = vec![];
for language in original_languages {
Expand Down Expand Up @@ -49,6 +83,75 @@ impl ToAPI {
pub struct FromAPI {}

impl FromAPI {
fn parse_url(actor_id: String) -> String {
let removed_begin = actor_id.strip_prefix("https://").unwrap_or(&actor_id);
let split_url: Vec<&str> = removed_begin.split('/').collect();
return format!("{}@{}", split_url.get(2).unwrap(), split_url.first().unwrap());
}

fn construct_blocked_users(original_profile: &site::GetSiteResponse) -> Vec<String> {
let original_blocks = &(original_profile.my_user.as_ref().unwrap().person_blocks);
let mut new_blocks = vec![];

for orig_block_view in original_blocks {
new_blocks.push(Self::parse_url(orig_block_view.target.actor_id.to_string()));
}

return new_blocks;
}

fn construct_blocked_communities(original_profile: &site::GetSiteResponse) -> Vec<String> {
let original_blocks = &(original_profile.my_user.as_ref().unwrap().community_blocks);
let mut new_blocks = vec![];

for orig_block_view in original_blocks {
new_blocks.push(Self::parse_url(orig_block_view.community.actor_id.to_string()));
}

return new_blocks;
}

fn construct_followed_communities(original_profile: &site::GetSiteResponse) -> Vec<String> {
let original_follows= &(original_profile.my_user.as_ref().unwrap().follows);
let mut new_follows = vec![];

for orig_follow_view in original_follows {
new_follows.push(Self::parse_url(orig_follow_view.community.actor_id.to_string()));
}

return new_follows;
}

pub fn construct_profile(original_profile: &site::GetSiteResponse) -> ProfileConfiguration {
let my_user = &(original_profile.my_user.as_ref().unwrap());
let local_user_view = &(my_user.local_user_view);
let local_user = &(local_user_view.local_user);
let person = &(local_user_view.person);

return ProfileConfiguration {
blocked_users: Self::construct_blocked_users(original_profile),
blocked_communities: Self::construct_blocked_communities(original_profile),
followed_communities: Self::construct_followed_communities(original_profile),
profile_settings: ProfileSettings {
show_nsfw: local_user.show_nsfw,
show_scores: local_user.show_scores,
theme: local_user.theme.clone(),
default_sort_type: Self::cast_sort_type(local_user.default_sort_type).to_string(),
default_listing_type: Self::cast_listing_type(local_user.default_listing_type).to_string(),
interface_language: local_user.interface_language.clone(),
show_avatars: local_user.show_avatars,
send_notifications_to_email: local_user.send_notifications_to_email,
bot_account: person.bot_account,
show_bot_accounts: local_user.show_bot_accounts,
show_read_posts: local_user.show_read_posts,
show_new_post_notifs: local_user.show_new_post_notifs,
discussion_languages: Self::cast_language_array(&my_user.discussion_languages),
open_links_in_new_tab: local_user.open_links_in_new_tab,
infinite_scroll_enabled: local_user.infinite_scroll_enabled,
},
};
}

pub fn cast_language_array(original_languages: &Vec<newtypes::LanguageId>) -> Vec<i32> {
let mut new_languages: Vec<i32> = vec![];
for language in original_languages {
Expand Down
Loading

0 comments on commit 1965893

Please sign in to comment.