Initial commit: working single-post application
This commit is contained in:
		
						commit
						7e61db4f3c
					
				| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					/target
 | 
				
			||||||
 | 
					.vscode
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "izzilis"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					authors = ["Emilis <grinding@graduate.org>"]
 | 
				
			||||||
 | 
					edition = "2018"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					elefren = { version = "0.22.0", features = ["toml"] }
 | 
				
			||||||
 | 
					rand = "0.8.4"
 | 
				
			||||||
 | 
					serde = "1.0.126"
 | 
				
			||||||
 | 
					serde_json = "1.0.64"
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,106 @@
 | 
				
			||||||
 | 
					use rand::Rng;
 | 
				
			||||||
 | 
					use std::{error::Error, io};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{generator, model, publish};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct IzzilisBot<T: model::SampleModel, U: publish::Publisher> {
 | 
				
			||||||
 | 
					    generator: generator::Generator<T>,
 | 
				
			||||||
 | 
					    publisher: U, // One day I'll figure out how to make this a vector with differing Publisher types
 | 
				
			||||||
 | 
					    loaded_samples: Vec<String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T, U> IzzilisBot<T, U>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    T: model::SampleModel,
 | 
				
			||||||
 | 
					    U: publish::Publisher,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    pub fn new(generator: generator::Generator<T>, publisher: U) -> IzzilisBot<T, U> {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            generator,
 | 
				
			||||||
 | 
					            publisher,
 | 
				
			||||||
 | 
					            loaded_samples: Vec::new(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn generate_samples(&mut self) -> Option<io::Error> {
 | 
				
			||||||
 | 
					        let lines_result = self.generator.generate_sample_lines();
 | 
				
			||||||
 | 
					        let lines = match lines_result {
 | 
				
			||||||
 | 
					            Ok(res) => res,
 | 
				
			||||||
 | 
					            Err(err) => return Some(err),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        self.loaded_samples = lines; // wtf happens to the original self.loaded_samples???????
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        None
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn publish(&mut self) -> Option<Box<dyn Error>> {
 | 
				
			||||||
 | 
					        if self.loaded_samples.len() < 5 {
 | 
				
			||||||
 | 
					            // Refresh samples. Either none have been generated so far,
 | 
				
			||||||
 | 
					            // or generated ones are stale.
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					            // This is a shit solution, but I'm going with it for v1
 | 
				
			||||||
 | 
					            // purely because I don't know the language well enough to be
 | 
				
			||||||
 | 
					            // confident in doing this via threads. Yet.
 | 
				
			||||||
 | 
					            self.generate_samples();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let sample_index = rand::thread_rng().gen_range(0..self.loaded_samples.len() - 1);
 | 
				
			||||||
 | 
					        let content = self.loaded_samples[sample_index].clone();
 | 
				
			||||||
 | 
					        self.loaded_samples.remove(sample_index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match self.publisher.publish(content) {
 | 
				
			||||||
 | 
					            Some(err) => Some(err),
 | 
				
			||||||
 | 
					            None => None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(tests)]
 | 
				
			||||||
 | 
					mod tests {
 | 
				
			||||||
 | 
					    use std::io::{self, ErrorKind};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use crate::{generator, model, publish};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct fake_sampler {
 | 
				
			||||||
 | 
					        should_ok: bool,
 | 
				
			||||||
 | 
					        ok_str: String,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct fake_publisher {
 | 
				
			||||||
 | 
					        should_ok: bool,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl model::SampleModel for fake_sampler {
 | 
				
			||||||
 | 
					        fn get_sample(&self) -> Result<String, io::Error> {
 | 
				
			||||||
 | 
					            if self.should_ok {
 | 
				
			||||||
 | 
					                return Ok(self.ok_str.clone());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Err(io::Error::new(ErrorKind::NotFound, "error"))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl publish::Publisher for fake_publisher {
 | 
				
			||||||
 | 
					        fn publish(&self, content: String) -> Option<Box<dyn std::error::Error>> {
 | 
				
			||||||
 | 
					            if self.should_ok {
 | 
				
			||||||
 | 
					                return None;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Some(Box::new(io::Error::new(ErrorKind::NotFound, "error")))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn generate_samples_populates() {
 | 
				
			||||||
 | 
					        let model_ok_string = String::from("model_ok");
 | 
				
			||||||
 | 
					        let model = fake_sampler {
 | 
				
			||||||
 | 
					            should_ok: true,
 | 
				
			||||||
 | 
					            ok_str: model_ok_string,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let gen = generator::Generator::new(model);
 | 
				
			||||||
 | 
					        let publish = fake_publisher { should_ok: true };
 | 
				
			||||||
 | 
					        let mut bot = super::IzzilisBot::new(gen, publish);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match bot.publish() {
 | 
				
			||||||
 | 
					            Some(_) => panic!("publish failed"),
 | 
				
			||||||
 | 
					            None => (),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,75 @@
 | 
				
			||||||
 | 
					use std::error::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize, Deserialize)]
 | 
				
			||||||
 | 
					pub struct Config {
 | 
				
			||||||
 | 
					    python_path: String,
 | 
				
			||||||
 | 
					    model_name: String,
 | 
				
			||||||
 | 
					    temperature: String,
 | 
				
			||||||
 | 
					    top_k: String,
 | 
				
			||||||
 | 
					    gpt_code_path: String,
 | 
				
			||||||
 | 
					    fediverse_base_url: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Config {
 | 
				
			||||||
 | 
					    pub fn default() -> Config {
 | 
				
			||||||
 | 
					        Config {
 | 
				
			||||||
 | 
					            python_path: String::from("/usr/bin/python3"),
 | 
				
			||||||
 | 
					            model_name: String::from("117M"),
 | 
				
			||||||
 | 
					            temperature: String::from("1"),
 | 
				
			||||||
 | 
					            top_k: String::from("40"),
 | 
				
			||||||
 | 
					            gpt_code_path: String::from("."),
 | 
				
			||||||
 | 
					            fediverse_base_url: String::from("https://lain.com"),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn from(path: String) -> Result<Config, Box<dyn Error>> {
 | 
				
			||||||
 | 
					        let file_bytes = std::fs::read(path)?;
 | 
				
			||||||
 | 
					        match serde_json::from_slice(&file_bytes) {
 | 
				
			||||||
 | 
					            Ok(res) => Ok(res),
 | 
				
			||||||
 | 
					            Err(err) => Err(Box::new(err)),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn save(&self, path: String) -> Option<Box<dyn Error>> {
 | 
				
			||||||
 | 
					        let cfg_json = match serde_json::to_vec(self) {
 | 
				
			||||||
 | 
					            Ok(res) => res,
 | 
				
			||||||
 | 
					            Err(err) => return Some(Box::new(err)),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match std::fs::write(path, &cfg_json) {
 | 
				
			||||||
 | 
					            Ok(_) => None,
 | 
				
			||||||
 | 
					            Err(err) => Some(Box::new(err)),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get a reference to the config's python path.
 | 
				
			||||||
 | 
					    pub fn python_path(&self) -> String {
 | 
				
			||||||
 | 
					        self.python_path.clone()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get a reference to the config's model name.
 | 
				
			||||||
 | 
					    pub fn model_name(&self) -> String {
 | 
				
			||||||
 | 
					        self.model_name.clone()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get a reference to the config's temperature.
 | 
				
			||||||
 | 
					    pub fn temperature(&self) -> String {
 | 
				
			||||||
 | 
					        self.temperature.clone()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get a reference to the config's top k.
 | 
				
			||||||
 | 
					    pub fn top_k(&self) -> String {
 | 
				
			||||||
 | 
					        self.top_k.clone()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get a reference to the config's gpt code path.
 | 
				
			||||||
 | 
					    pub fn gpt_code_path(&self) -> String {
 | 
				
			||||||
 | 
					        self.gpt_code_path.clone()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn fediverse_base_url(&self) -> String {
 | 
				
			||||||
 | 
					        self.fediverse_base_url.clone()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,32 @@
 | 
				
			||||||
 | 
					use std::io;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::model;
 | 
				
			||||||
 | 
					const SAMPLE_SPLIT_WORD: &str = "<|endoftext|>";
 | 
				
			||||||
 | 
					const SAMPLE_SAMPLE_LINE: &str =
 | 
				
			||||||
 | 
					    "======================================== SAMPLE 1 ========================================";
 | 
				
			||||||
 | 
					pub struct Generator<T: model::SampleModel> {
 | 
				
			||||||
 | 
					    model: T,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Why did this fucking shit take so long to sort out??
 | 
				
			||||||
 | 
					impl<T> Generator<T>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    T: model::SampleModel,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    pub fn new(model: T) -> Generator<T> {
 | 
				
			||||||
 | 
					        Self { model }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn generate_sample_lines(&self) -> Result<Vec<String>, io::Error> {
 | 
				
			||||||
 | 
					        let unwashed_sample = self.model.get_sample()?;
 | 
				
			||||||
 | 
					        // Cursed, I just wanted a Select
 | 
				
			||||||
 | 
					        let mut washed_sample: Vec<String> = Vec::new();
 | 
				
			||||||
 | 
					        unwashed_sample
 | 
				
			||||||
 | 
					            .replace(SAMPLE_SAMPLE_LINE, "")
 | 
				
			||||||
 | 
					            .split(SAMPLE_SPLIT_WORD)
 | 
				
			||||||
 | 
					            .into_iter()
 | 
				
			||||||
 | 
					            .for_each(|elem| washed_sample.push(elem.trim().to_string()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(washed_sample)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,50 @@
 | 
				
			||||||
 | 
					use std::{error::Error, process};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod bot;
 | 
				
			||||||
 | 
					mod config;
 | 
				
			||||||
 | 
					mod generator;
 | 
				
			||||||
 | 
					mod model;
 | 
				
			||||||
 | 
					mod publish;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CONFIG_PATH: &str = "bot_config.json";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
 | 
					    let cfg = match config::Config::from(CONFIG_PATH.to_string()) {
 | 
				
			||||||
 | 
					        Ok(cfg) => cfg,
 | 
				
			||||||
 | 
					        Err(_) => {
 | 
				
			||||||
 | 
					            println!(
 | 
				
			||||||
 | 
					                "Failed reading config at [{}], writing default",
 | 
				
			||||||
 | 
					                CONFIG_PATH
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            match config::Config::default().save(CONFIG_PATH.to_string()) {
 | 
				
			||||||
 | 
					                Some(err) => println!("Failed writing file to {}: {}", CONFIG_PATH, err),
 | 
				
			||||||
 | 
					                None => (),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            process::exit(1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let gpt_model = model::GPTSampleModel::new(
 | 
				
			||||||
 | 
					        cfg.python_path(),
 | 
				
			||||||
 | 
					        cfg.gpt_code_path(),
 | 
				
			||||||
 | 
					        vec![
 | 
				
			||||||
 | 
					            "generate_unconditional_samples.py".to_string(),
 | 
				
			||||||
 | 
					            "--model_name".to_string(),
 | 
				
			||||||
 | 
					            cfg.model_name(),
 | 
				
			||||||
 | 
					            "--temperature".to_string(),
 | 
				
			||||||
 | 
					            cfg.temperature(),
 | 
				
			||||||
 | 
					            "--top_k".to_string(),
 | 
				
			||||||
 | 
					            cfg.top_k(),
 | 
				
			||||||
 | 
					            "--nsamples".to_string(),
 | 
				
			||||||
 | 
					            "1".to_string(),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    let publisher = publish::FediversePublisher::new(cfg.fediverse_base_url())?;
 | 
				
			||||||
 | 
					    let gen = generator::Generator::new(gpt_model);
 | 
				
			||||||
 | 
					    let mut bot = bot::IzzilisBot::new(gen, publisher);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match bot.publish() {
 | 
				
			||||||
 | 
					        Some(err) => Err(err),
 | 
				
			||||||
 | 
					        None => Ok(()),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					use std::{io, process::Command};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub trait SampleModel {
 | 
				
			||||||
 | 
					    fn get_sample(&self) -> Result<String, io::Error>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct GPTSampleModel {
 | 
				
			||||||
 | 
					    python_command: String,
 | 
				
			||||||
 | 
					    command_working_path: String,
 | 
				
			||||||
 | 
					    command_args: Vec<String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl SampleModel for GPTSampleModel {
 | 
				
			||||||
 | 
					    fn get_sample(&self) -> Result<String, io::Error> {
 | 
				
			||||||
 | 
					        let cmd_output = Command::new(&self.python_command)
 | 
				
			||||||
 | 
					            .current_dir(&self.command_working_path)
 | 
				
			||||||
 | 
					            .args(&self.command_args)
 | 
				
			||||||
 | 
					            .output()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(String::from_utf8_lossy(&cmd_output.stdout).to_string())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl GPTSampleModel {
 | 
				
			||||||
 | 
					    pub fn new(
 | 
				
			||||||
 | 
					        python_command: String,
 | 
				
			||||||
 | 
					        command_working_path: String,
 | 
				
			||||||
 | 
					        command_args: Vec<String>,
 | 
				
			||||||
 | 
					    ) -> GPTSampleModel {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            python_command: python_command,
 | 
				
			||||||
 | 
					            command_working_path: command_working_path,
 | 
				
			||||||
 | 
					            command_args: command_args,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,62 @@
 | 
				
			||||||
 | 
					use std::error::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use elefren::{
 | 
				
			||||||
 | 
					    helpers::{cli, toml},
 | 
				
			||||||
 | 
					    scopes::Scopes,
 | 
				
			||||||
 | 
					    status_builder::Visibility,
 | 
				
			||||||
 | 
					    Language, Mastodon, MastodonClient, Registration, StatusBuilder,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					pub trait Publisher {
 | 
				
			||||||
 | 
					    fn publish(&self, content: String) -> Option<Box<dyn Error>>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct FediversePublisher {
 | 
				
			||||||
 | 
					    client: Mastodon,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FediversePublisher {
 | 
				
			||||||
 | 
					    pub fn new(fedi_url: String) -> Result<FediversePublisher, Box<dyn Error>> {
 | 
				
			||||||
 | 
					        let fedi = if let Ok(data) = toml::from_file("fediverse.toml") {
 | 
				
			||||||
 | 
					            Mastodon::from(data)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            register(fedi_url)?
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(Self { client: fedi })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Publisher for FediversePublisher {
 | 
				
			||||||
 | 
					    fn publish(&self, content: String) -> Option<Box<dyn Error>> {
 | 
				
			||||||
 | 
					        let status_build_result = StatusBuilder::new()
 | 
				
			||||||
 | 
					            .status(&content)
 | 
				
			||||||
 | 
					            // .visibility(Visibility::Direct)
 | 
				
			||||||
 | 
					            .visibility(Visibility::Public)
 | 
				
			||||||
 | 
					            .sensitive(false)
 | 
				
			||||||
 | 
					            .language(Language::Eng)
 | 
				
			||||||
 | 
					            .build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let status = match status_build_result {
 | 
				
			||||||
 | 
					            Ok(status) => status,
 | 
				
			||||||
 | 
					            Err(err) => return Some(Box::new(err)),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        println!("Posting status [{}] to fediverse", &content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match self.client.new_status(status) {
 | 
				
			||||||
 | 
					            Ok(_) => None,
 | 
				
			||||||
 | 
					            Err(err) => Some(Box::new(err)),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn register(fedi_url: String) -> Result<Mastodon, Box<dyn Error>> {
 | 
				
			||||||
 | 
					    let registration = Registration::new(fedi_url)
 | 
				
			||||||
 | 
					        .client_name("izzilis")
 | 
				
			||||||
 | 
					        .scopes(Scopes::write_all())
 | 
				
			||||||
 | 
					        .build()?;
 | 
				
			||||||
 | 
					    let fediverse = cli::authenticate(registration)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Save app data for using on the next run.
 | 
				
			||||||
 | 
					    toml::to_file(&*fediverse, "fediverse.toml")?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(fediverse)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue