Changes for 0.2.0

This commit is contained in:
KaitlynEthylia 2023-08-17 15:59:46 +01:00
parent 2c4fcd7bc0
commit f885ab99c5
15 changed files with 580 additions and 131 deletions

13
.editorconfig Normal file
View file

@ -0,0 +1,13 @@
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_size = 4
indent_style = tab
tab_width = 4
[*.md]
max_line_length = 70

38
CHANGELOG.md Normal file
View file

@ -0,0 +1,38 @@
# 0.2.0
## Added
- Lua values may now be set to functions that will return the desired
type. This prevents code being run multiple times when multiple Lua
VMs are spawned.
- The `watch` function has been added as a builtin helper function
for executing a shell command and following the output. The
function takes a shell command as the first arg, and optionally a
value to use if the command fails to start.
- When built with the `unsafe` feature flag, the `--safe` flag can be
passed to the command line to only load safe libraries anyway.
- The `--dry-run` command line flag can be passed to prevent
attempting to connect to Discord.
- Fish and Zsh completions are now provided with the release.
- Systemd, Runit, and OpenRC template services can be found in the
[etc](/etc) directory.
## Changed
- ClientID has been renamed to ApplicationID to better reflect
Discord's own usage and reduce ambiguity.
- Output has been cleaned up, making use of
[simplelog](https://lib.rs/simplelog).
- Command help text has been improved.
## Fixed
- The output binary is now named `disco`. Previously it was
mistakenly called `disco-rpc` by default.

130
Cargo.lock generated
View file

@ -43,9 +43,9 @@ dependencies = [
[[package]]
name = "anstyle-wincon"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c"
dependencies = [
"anstyle",
"windows-sys",
@ -65,9 +65,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.3.3"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "bstr"
@ -95,9 +95,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.3.19"
version = "4.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d"
checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd"
dependencies = [
"clap_builder",
"clap_derive",
@ -106,9 +106,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.3.19"
version = "4.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1"
checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa"
dependencies = [
"anstream",
"anstyle",
@ -140,6 +140,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "deranged"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
[[package]]
name = "dirs"
version = "5.0.1"
@ -162,13 +168,15 @@ dependencies = [
]
[[package]]
name = "disco"
version = "1.0.0"
name = "disco-rpc"
version = "0.2.0"
dependencies = [
"clap",
"dirs",
"discord-rich-presence",
"log",
"mlua",
"simplelog",
]
[[package]]
@ -256,6 +264,12 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.5.0"
@ -285,6 +299,15 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.18.0"
@ -349,11 +372,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.6"
version = "0.38.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f"
checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f"
dependencies = [
"bitflags 2.3.3",
"bitflags 2.4.0",
"errno",
"libc",
"linux-raw-sys",
@ -394,6 +417,17 @@ dependencies = [
"serde",
]
[[package]]
name = "simplelog"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369"
dependencies = [
"log",
"termcolor",
"time",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -411,6 +445,15 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.44"
@ -431,6 +474,36 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea"
dependencies = [
"deranged",
"itoa",
"libc",
"num_threads",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]]
name = "time-macros"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd"
dependencies = [
"time-core",
]
[[package]]
name = "unicode-ident"
version = "1.0.11"
@ -458,6 +531,37 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"

View file

@ -1,17 +1,24 @@
[package]
name = "disco-rpc"
version = "0.1.0"
version = "0.2.0"
description = "A customisable client for Discord rich presence using simple Lua configuration."
authors = ["Kaitlyn~Ethylia <kaitlyyn.ethylia@proton.me>"]
edition = "2021"
repository = "https://github.com/KaitlynEthylia/Disco"
license = "Unlicense"
exclude = ["/etc/*"]
[dependencies]
clap = { version = "4.3.19", features = ["derive"] }
clap = { version = "4.3.21", features = ["derive", "env", "string"] }
dirs = "5.0.1"
discord-rich-presence = "0.2.3"
log = "0.4.20"
mlua = { version = "0.8.9", features = ["lua54", "send"] }
simplelog = "0.12.1"
[features]
unsafe = []
[[bin]]
name = "disco"
path = "src/main.rs"

View file

@ -11,7 +11,7 @@
[![Crates.io](https://img.shields.io/crates/v/disco-rpc?color=%23f7b679&logo=rust&style=for-the-badge)](https://crates.io/crates/disco-rpc)
[![Unlicense](https://img.shields.io/crates/l/terny?color=bfdfff&logo=unlicense&style=for-the-badge)](https://unlicense.org/)
Disco is a customisable client for Discord rich presence using
Disco is a customisable client for Discord rich presence using
simple Lua configuration.
</div>
@ -69,13 +69,16 @@ programme from running and print the location that it would otherwise
look for a configuration file.
The other flags that actually affect the programme are:
**-c, --config <CONFIG>**: Overrides the default path to look for configuration.
**-c, --config <FILE>**: Override the default configuration path.
<br />
**-i, --client-id <CLIENT_ID>**: Sets the ID of the application to connect as. Takes precedent over Lua configuration.
**-i, --application-id <ID>**: Set the ID of the Discord application to connect to.
<br />
**-r, --retry-after <DELAY>**: If connecting to Discord fails, retry after DELAY seconds [default: 0]
**-r, --retry-after <DELAY>**: Retry after a failed connection.
<br />
**-q, --quiet ...**: Disables printing excess information.
<br />
**-d, --dry-run**: Parse the config but don't connect to Discord.
<br />
**-q, --quiet**: Don't print any text to the console.
<a id="configuration" />
@ -151,6 +154,11 @@ returning. Once it stops, the final value remains.
This example is contrived, but a more complex example can be seen
in [the example section](#example)
Additionally. As well as assigning a value directly, a function can be
given which will immediately return the value. This ensured that the
value is evaluated only once, despite the application having to launch
multiple Lua VMs.
Disco may also call external lua libraries, however, if that
library requires C libraries, the `unsafe` feature flag will need to
be enabled.
@ -174,9 +182,9 @@ but I have modified it slightly to demonstrate some features that
I didn't use, such as polling.
```lua
-- ID of the client I created for ArchLinux via
-- ID of the application I created for ArchLinux via
-- https://discord.com/developers/applications
ClientID = 1137762526541656105
ApplicationID = 1137762526541656105
-- Display the rich presence
Active = true
@ -219,17 +227,6 @@ Button1 = {
}
```
## Before Release
### The Result
Things that intend to be done before i could be happy calling this a
1.0.0 project.
- [ ] Provide extra utilitly functions to make some tasks easier.
- [ ] Test compaility on non x86_64-linux-gnu machines, and fix where possible.
- [ ] Provide bash, zsh, and fish completions with the releases.
- [ ] Provide template systemd and potentially runit and openrc services for running on startup.
- [ ] Fix bugs that are more than likely to show up, and provide friendlier error handling.
None of these goals are particularly quick and easy, so they are
likely to only end up completed if enough interest is shown in the
tool.
![Discord Rich Presence](/etc/images/example.png)

BIN
etc/assets/example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,9 @@
complete -c disco -f
complete -c disco -s 'V' -l 'version' -d "Print version"
complete -c disco -s 'h' -l 'help' -d "Print help"
complete -c disco -s 'c' -l 'config' -d "Override the default configuration path." -F
complete -c disco -s 'i' -l 'application-id' -d "Set the ID of the Discord application to connect to."
complete -c disco -s 'r' -l 'retry-after' -d "Retry after a failed connection."
complete -c disco -s 'q' -l 'quiet' -d "Disables printing excess information." -x -a "0 1 2"
complete -c disco -s 'p' -l 'print-config-path' -d "Print the default configuration location."
complete -c disco -s 'd' -l 'dry-run' -d "Parse the config but don't connect to Discord."

View file

@ -0,0 +1,19 @@
#compdef disco
# Save this file as _exa in /usr/local/share/zsh/site-functions or in any
# other folder in $fpath. E.g. save it in a folder called ~/.zfunc and add a
# line containing `fpath=(~/.zfunc $fpath)` somewhere before `compinit` in your
# ~/.zshrc.
__disco() {
_arguments -s -S \
"(- *)"{-V,--version}"[Print version]" \
"(- *)"{-h,--help}"[Print help]" \
{-c,--config}"[Override the default configuration path.]:(file)" \
{-i,--application-id}"[Set the ID of the Discord application to connect to.]:(id)" \
{-r,--retry-after}"[Retry after a failed connection.]:(seconds)" \
{-q,--quiet}"[Disables printing excess information.]:(quiet):(0 1 2)" \
{-p,--print-config-path}"[Print the default configuration location.]" \
{-d,--dry-run}"[Parse the config but don't connect to Discord.]"
}
__disco

23
etc/services/openrc/disco Normal file
View file

@ -0,0 +1,23 @@
#!/sbin/openrc-run
#
# NOTE: This is a service for the openrc init system.
#
# How to use this service:
#
# 1. Place this file in the `/etc/init.d/' directory
#
# 2. Start the service
#
# rc-service disco start
#
# 3. If everything works as intended, add service to default runlevel as it will
# run after boot runlevel
#
# rc-update add disco default
#
# The paths below should be absolute paths.
command=/path/to/disco
command_args="--retry-after <int>"
command_background="true"

31
etc/services/runit/run Normal file
View file

@ -0,0 +1,31 @@
#!/bin/sh
#
# NOTE: This is a service for the runit init system! If you are using
# something else (openrc, sysvinit, systemd, ...) then you do not
# need to use this file.
#
# How to use this service:
#
# 1. Place this file in the `/etc/sv/disco' directory. You will need
# to create this directory; this is most easily done with `xmksv'
# from the `xtools' package:
#
# xmksv disco
#
# 2. Symlink to `/var/service':
#
# ln -s /etc/sv/disco /var/service
#
# Note 2: xmksv can create by default a `down` file that disable
# disco to startup with system, if that happens just delete this file;
# see [2] for further details.
#
# The paths below should be absolute paths. You can put environment
# variables in a `conf' file that's in the same directory as this file
# and refer to them here if you want; see [1] for further details.
#
# [1] https://docs.voidlinux.org/config/services/index.html#service-directories
# [2] https://docs.voidlinux.org/config/services/index.html#enabling-services
exec 1>&2
exec /path/to/disco --retry-after <int> || exit 1

View file

@ -0,0 +1,5 @@
[Unit]
Description=Disco - Discord Rich Presence client
[Service]
ExecStart=/path/to/disco --retry-after <int>

152
src/command.rs Normal file
View file

@ -0,0 +1,152 @@
use std::{fs, path::PathBuf};
use log::{error, info, warn};
use simplelog::{ColorChoice, Config, LevelFilter, TermLogger, TerminalMode};
use clap::{value_parser, Parser};
use crate::Disco;
#[derive(Parser, Debug)]
//TODO disco2 vs disco vs disco-rpc
// Waiting on https://github.com/clap-rs/clap/issues/3221
#[command(author, version, about, long_about = None)]
struct Command {
#[arg(
short,
long,
env = "DISCO_CONFIG",
value_name = "FILE",
help = "Override the default configuration path.",
default_value = get_path().into_os_string()
)]
config: PathBuf,
#[arg(
short = 'i',
long,
env = "DISCO_APPLICATION_ID",
value_name = "ID",
help = "Set the ID of the Discord application to connect to.",
long_help = "Set the ID of the Discord application to connect to. \
This value takes precedent over the value set in Lua.",
value_parser = value_parser!(u64).range(10000000000000000..=999999999999999999)
)]
application_id: Option<u64>,
#[arg(
short,
long,
env = "DISCO_RETRY_AFTER",
value_name = "DELAY",
default_value_t = 0,
help = "Retry after a failed connection.",
long_help = "If connecting to Discord fails, keep retrying every DELAY \
seconds."
)]
retry_after: usize,
#[arg(
short,
long,
env = "DISCO_QUIET",
action = clap::ArgAction::Count,
help = "Disables printing excess information.",
long_help = "Disable printing information about the running process.
Set twice to disable output completely, including errors.",
value_parser = value_parser!(u8).range(0..=2)
)]
quiet: u8,
#[arg(
short,
long,
help = "Print the default configuration location.",
long_help = "Halts normal execution and prints the location that Disco \
will attempt to use for configuration by default."
)]
print_config_path: bool,
#[arg(short, long, help = "Parse the config but don't connect to Discord.")]
dry_run: bool,
#[cfg(feature = "unsafe")]
#[arg(
short,
long,
help = "Run the Lua VM in safe mode.",
long_help = "Run the Lua VM in safe mode, meaning it will not be able \
to load any C libraries. This option is only available when compiled \
with the unsafe feature flag."
)]
safe: bool,
}
fn get_path() -> PathBuf {
dirs::config_local_dir()
.unwrap_or(PathBuf::new())
.join("disco.lua")
}
fn validate_path(pathbuf: &PathBuf) -> bool {
let path = pathbuf.as_path();
path.exists() && path.is_file()
}
macro_rules! unwrap_error {
($expr:expr, $msg:literal) => {
match $expr {
Ok(val) => val,
Err(_) => {
error!($msg);
return None;
},
}
};
}
pub fn init() -> Option<Disco> {
let args = Command::parse();
let log_level = match args.quiet {
0 => LevelFilter::Debug,
1 => LevelFilter::Warn,
2.. => LevelFilter::Off,
};
TermLogger::init(
log_level,
Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto,
)
.expect("Failed to setup logging");
let valid_path = validate_path(&args.config);
if args.print_config_path {
info!("Config path is {}", args.config.display());
if !valid_path {
warn!("Config file does not exist");
};
return None;
}
if !valid_path {
error!("Config file does not exist");
return None;
};
let data = unwrap_error!(fs::read(&args.config), "Failed to open config file.");
let data = unwrap_error!(String::from_utf8(data), "Config is not valid UTF-8.");
return Some(Disco {
retry_after: args.retry_after,
dry_run: args.dry_run,
application_id: args.application_id,
config_data: data,
#[cfg(feature = "unsafe")]
safe: args.safe,
});
}

11
src/library.rs Normal file
View file

@ -0,0 +1,11 @@
pub static DISCO_LIB: &str = "
function watch(command, err)
return coroutine.create(function()
local handle = io.popen(_)
if not handle then return err end
for line in handle:lines() do
coroutine.yield(line)
end
end)
end
";

View file

@ -29,6 +29,11 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Variable<T> {
}
},
"thread" => Self::Listen,
"function" => {
let fun = LuaFunction::from_lua(lua_value, lua)?;
let val = fun.call(())?;
Self::from_lua(val, lua)?
},
_ => Self::Static(T::from_lua(lua_value, lua)?),
})
}
@ -40,6 +45,7 @@ impl<T: Clone + for<'lua> FromLua<'lua> + Send + 'static> Variable<T> {
name: &'static str,
data: String,
dofun: F,
#[cfg(feature = "unsafe")] safe: bool,
) -> Option<JoinHandle<()>> {
match self {
Self::Static(val) => {
@ -47,7 +53,10 @@ impl<T: Clone + for<'lua> FromLua<'lua> + Send + 'static> Variable<T> {
None
},
Self::Poll(rate) => Some(thread::spawn(move || {
let lua = get_lua();
let lua = get_lua(
#[cfg(feature = "unsafe")]
safe,
);
lua.load(&data).exec().unwrap();
let fun: LuaFunction = lua
.globals()
@ -62,7 +71,10 @@ impl<T: Clone + for<'lua> FromLua<'lua> + Send + 'static> Variable<T> {
}
})),
Self::Listen => Some(thread::spawn(move || {
let lua = get_lua();
let lua = get_lua(
#[cfg(feature = "unsafe")]
safe,
);
lua.load(&data).exec().unwrap();
let thread: LuaThread = lua.globals().get(name).unwrap();
loop {
@ -76,6 +88,7 @@ impl<T: Clone + for<'lua> FromLua<'lua> + Send + 'static> Variable<T> {
}
}
#[cfg(not(feature = "unsafe"))]
macro_rules! watchtype {
($var:ident, $ty:ty, $name:ident, $send:ident, $ctx:ident, $env:ident) => {
$env.get::<_, Variable<$ty>>($name)?
@ -85,6 +98,7 @@ macro_rules! watchtype {
};
}
#[cfg(not(feature = "unsafe"))]
pub fn create_watcher(
name: &'static str,
send: Sender<ActivityData>,
@ -103,3 +117,38 @@ pub fn create_watcher(
ActivityDataTag::SmallImage => watchtype!(SmallImage, Image, name, send, ctx, env),
})
}
#[cfg(feature = "unsafe")]
macro_rules! watchtype {
($var:ident, $ty:ty, $name:ident, $send:ident, $ctx:ident, $env:ident, $safe:ident) => {
$env.get::<_, Variable<$ty>>($name)?.watch(
$name,
$ctx.clone(),
move |val| $send.send(ActivityData::$var(val)).unwrap(),
$safe,
)
};
}
#[cfg(feature = "unsafe")]
pub fn create_watcher(
name: &'static str,
send: Sender<ActivityData>,
ctx: &String,
env: &LuaTable,
tag: &ActivityDataTag,
safe: bool,
) -> Result<Option<JoinHandle<()>>, LuaError> {
Ok(match tag {
ActivityDataTag::Active => watchtype!(Active, bool, name, send, ctx, env, safe),
ActivityDataTag::State => watchtype!(State, String, name, send, ctx, env, safe),
ActivityDataTag::Details => watchtype!(Details, String, name, send, ctx, env, safe),
ActivityDataTag::Timestamp => watchtype!(Timestamp, Timestamp, name, send, ctx, env, safe),
ActivityDataTag::FirstButton => watchtype!(FirstButton, Button, name, send, ctx, env, safe),
ActivityDataTag::SecondButton => {
watchtype!(SecondButton, Button, name, send, ctx, env, safe)
},
ActivityDataTag::LargeImage => watchtype!(LargeImage, Image, name, send, ctx, env, safe),
ActivityDataTag::SmallImage => watchtype!(SmallImage, Image, name, send, ctx, env, safe),
})
}

View file

@ -1,13 +1,10 @@
use std::sync::mpsc;
use std::time::Duration;
use std::{env, path::PathBuf};
use std::{fs, thread};
use std::{process, thread};
use clap::Parser;
use dirs::config_dir;
use discord_rich_presence::activity::{Activity, Assets, Button, Timestamps};
use discord_rich_presence::{DiscordIpc, DiscordIpcClient};
use log::{error, info, warn};
use mlua::Lua;
use crate::activity::ActivityData;
@ -16,97 +13,88 @@ use crate::activity::{Button as DiscoButton, Image, Timestamp};
use crate::lua::create_watcher;
mod activity;
mod command;
mod library;
mod lua;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Command {
/// Overrides the default path to look for configuration.
#[arg(short, long)]
config: Option<PathBuf>,
/// Sets the ID of the application to connect as. Takes prescedent over Lua configuration.
#[arg(short = 'i', long)]
client_id: Option<String>,
/// If connecting to Discord fails, retry after DELAY seconds.
#[arg(short, long, value_name = "DELAY", default_value_t = 0)]
retry_after: u64,
/// Don't print any text to the console.
#[arg(short, long)]
quiet: bool,
#[arg(short, long)]
print_config_path: bool,
#[derive(Debug)]
pub struct Disco {
pub retry_after: usize,
pub dry_run: bool,
pub application_id: Option<u64>,
pub config_data: String,
#[cfg(feature = "unsafe")]
pub safe: bool,
}
pub fn get_lua() -> Lua {
pub fn get_lua(#[cfg(feature = "unsafe")] safe: bool) -> Lua {
#[cfg(not(feature = "unsafe"))]
return Lua::new();
let lua = Lua::new();
#[cfg(feature = "unsafe")]
unsafe {
return Lua::unsafe_new();
let lua = if !safe {
unsafe { Lua::unsafe_new() }
} else {
Lua::new()
};
if let Err(_) = lua.load(library::DISCO_LIB).exec() {
warn!("Failed to load provided builtin functions, only the Lua standard library will be available");
};
lua
}
fn main() {
let args = Command::parse();
let path = match args.config {
Some(path) => path,
None => env::var("DISCO_CONFIG")
.map(|val| PathBuf::from(val))
.unwrap_or(
config_dir()
.expect(
"Could not find a place to look for config. Please
specify a file in the command line or set
DISCO_CONFIG variable.",
)
.join("disco.lua"),
),
let args = match command::init() {
Some(disco) => disco,
None => process::exit(0),
};
if args.print_config_path {
println!("{:?}", path);
return;
}
let data = fs::read(&path).expect(&format!("Failed to read config file at {path:?}"));
let file = String::from_utf8(data).expect("Contents of config file is not valid UTF-8");
let lua = get_lua();
lua.load(&file).exec().unwrap();
let lua = get_lua(
#[cfg(feature = "unsafe")]
args.safe,
);
lua.load(&args.config_data).exec().unwrap();
let env = lua.globals();
let client_id = match args.client_id {
Some(id) => id,
None => env
.get("ClientID")
.expect("No client id available. Set ClientID in {path} or with --client-id"),
let application_id = match args.application_id {
Some(id) => id.to_string(),
None => match env.get("ApplicationID") {
Ok(application_id) => application_id,
Err(_) => {
error!("No application id available. Set ApplicationID in disco.lua or with --application-id");
process::exit(0);
},
},
};
if !args.quiet {
println!("Client ID: {client_id}");
}
info!("Application ID: {application_id}");
let mut client =
DiscordIpcClient::new(&client_id).expect("Failed to create Discord IPC Client");
let mut ret = client.connect();
match args.retry_after {
0 => {
if let Err(_) = ret {
panic!("Failed to connect to Discord IPC. Please make sure Discord is open.");
}
},
n => {
while let Err(_) = ret {
println!("Failed to connect to Discord IPC. Retrying in {n} seconds...");
thread::sleep(Duration::from_secs(n));
ret = client.connect();
}
let mut client = match args.dry_run {
false => Some(
DiscordIpcClient::new(&application_id).expect("Failed to create Discord IPC Client"),
),
true => {
info!("Performing dry run. Won't attempt to connect to Discord");
None
},
};
if let Some(ref mut client) = client {
let mut ret = client.connect();
match args.retry_after {
0 => {
if let Err(_) = ret {
error!("Failed to connect to Discord IPC. Please make sure Discord is open.");
process::exit(0);
}
},
n => {
while let Err(_) = ret {
warn!("Failed to connect to Discord IPC. Retrying in {n} seconds...");
thread::sleep(Duration::from_secs(n as u64));
ret = client.connect();
}
},
};
};
let (send, recv) = mpsc::channel::<ActivityData>();
@ -122,11 +110,17 @@ fn main() {
];
values.iter().for_each(|(name, ty)| {
let ret = create_watcher(name, send.clone(), &file, &env, &ty);
let ret = create_watcher(
name,
send.clone(),
&args.config_data,
&env,
&ty,
#[cfg(feature = "unsafe")]
args.safe,
);
if let Err(_) = ret {
if !args.quiet {
println!("No value for {name}");
}
warn!("No value for {name}");
}
});
@ -135,13 +129,13 @@ fn main() {
loop {
match recv.recv() {
Ok(val) => {
if !args.quiet {
println!("New Value: {val:?}");
}
info!("New Value: {val:?}");
match val {
ActivityData::Active(val) => {
if !val {
let _ = client.clear_activity();
if let Some(ref mut client) = client {
if !val {
let _ = client.clear_activity();
}
}
activity.active = val
},
@ -153,12 +147,12 @@ fn main() {
ActivityData::LargeImage(val) => activity.large_image = Some(val),
ActivityData::SmallImage(val) => activity.small_image = Some(val),
}
activity.process(&mut client, args.quiet);
if let Some(ref mut client) = client {
activity.process(client);
}
},
Err(_) => {
if !args.quiet {
println!("Exiting...");
}
info!("Exiting...");
break;
},
}
@ -191,7 +185,7 @@ impl DiscoActivity {
}
}
fn process(&self, client: &mut DiscordIpcClient, quiet: bool) {
fn process(&self, client: &mut DiscordIpcClient) {
if self.active {
let mut activity = Activity::new();
@ -247,9 +241,6 @@ impl DiscoActivity {
activity = activity.assets(assets);
}
if !quiet {
println!("DEBUG: {self:#?}");
}
client.set_activity(activity).unwrap();
}
}