mirror of
https://gitea.0xace.cc/rust/github-release-bot.git
synced 2024-11-24 21:36:40 +00:00
first commit
This commit is contained in:
commit
c7d2b176f6
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@ -0,0 +1,5 @@
|
||||
**
|
||||
!Cargo.toml
|
||||
!Cargo.lock
|
||||
!src
|
||||
!docker
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target
|
||||
/data
|
||||
config.yaml
|
1692
Cargo.lock
generated
Normal file
1692
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "github-release-bot"
|
||||
version = "0.1.0"
|
||||
authors = ["ace <ace@0xace.cc>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_yaml = "0.8"
|
||||
octocrab = "0.8"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
futures = "0.3"
|
||||
clap = "3.0.0-beta.2"
|
||||
#telegram-bot = { git = "https://github.com/telegram-rs/telegram-bot", rev = "07a9f9a1c76eaab2259bdc6241691187a46d69d1" }
|
||||
telegram-bot = { git = "https://github.com/telegram-rs/telegram-bot", rev = "65ad5cfd578e9a1260ce6daac714eb2153c0bec7" }
|
||||
log = "0.4"
|
||||
stderrlog = "0.5"
|
||||
rand = "0.8"
|
||||
chrono ="0.4"
|
15
Makefile
Normal file
15
Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
VERSION=0.1.0
|
||||
IMAGE=github-release-bot
|
||||
ifneq ($(REGISTRY),)
|
||||
_REGISTRY =$(REGISTRY)/
|
||||
endif
|
||||
|
||||
.PHONY: build push all
|
||||
|
||||
build:
|
||||
docker build -t $(_REGISTRY)$(IMAGE):$(VERSION) -f docker/Dockerfile .
|
||||
|
||||
push:
|
||||
docker push $(_REGISTRY)$(IMAGE):$(VERSION)
|
||||
|
||||
all: build push
|
61
README.md
Normal file
61
README.md
Normal file
@ -0,0 +1,61 @@
|
||||
# GitHub release bot
|
||||
Telegram bot for GitHub releases notification. Bot simply fetches latest release and send notification.
|
||||
|
||||
**Warning**: GitHub will ban you with small interval and spread time. Use adequate interval and spread time or use TOR.
|
||||
|
||||
Interval - set how often check GitHub for new releases in seconds. Default - 28800 seconds (8 hours).
|
||||
|
||||
Spread - time in seconds for random offset calculation per repo within interval.
|
||||
Default 600 seconds (10 minutes). Offset calculated as RANDOM(u64) % SPREAD.
|
||||
|
||||
With default values bot will launch main loop check every 8 hours and get latest release within (RANDOM(u64) % 600) seconds per repo.
|
||||
|
||||
## Example config.yaml
|
||||
```yaml
|
||||
- owner: go-gitea
|
||||
repo: gitea
|
||||
- owner: kubernetes-sigs
|
||||
repo: kubespray
|
||||
```
|
||||
## Binary
|
||||
### Build binary
|
||||
```ShellSession
|
||||
cargo build --release
|
||||
```
|
||||
### Run binary
|
||||
```ShellSession
|
||||
github-release-bot \
|
||||
--token ${TOKEN} --chatid ${CHATID} \
|
||||
--config ${CONFIG} --datadir ${DATADIR} \
|
||||
--interval ${INTERVAL} --spread ${SPREAD}
|
||||
```
|
||||
### Binary params
|
||||
- token - required, Telegram token
|
||||
- chatid - required, Telegram chat id
|
||||
- config - file required, default config file name "config.yaml"
|
||||
- interval - optional, default 28800 seconds (8 hours)
|
||||
- spread - optional, default 600 seconds (10 minutes)
|
||||
- datadir - optional, dir for storing files with latest releases, default "data"
|
||||
|
||||
## Docker
|
||||
### Docker build
|
||||
```ShellSession
|
||||
make build
|
||||
```
|
||||
### Docker run
|
||||
```ShellSession
|
||||
docker run -d -v ./config.yaml:/opt/config.yaml -v ./data:/opt/data registry.0xace.cc/ghp/github-release-bot:latest \
|
||||
-e TOKEN=${TOKEN} -e CHATID=${CHATID} \
|
||||
-e CONFIG=${CONFIG} -e DATADIR=${DATADIR} \
|
||||
-e INTERVAL=${INTERVAL} -e SPREAD=${SPREAD}
|
||||
```
|
||||
### Docker params
|
||||
OS ENV used to pass arguments:
|
||||
- TOKEN - required, Telegram token
|
||||
- CHATID - required, Telegram chat id
|
||||
- CONFIG - file required, default config file name "config.yaml"
|
||||
- INTERVAL - optional, default 28800 seconds (8 hours)
|
||||
- SPREAD - optional, default 600 seconds (10 minutes)
|
||||
- DATADIR - optional, dir for storing files with latest releases, default "data"
|
||||
|
||||
|
11
docker/Dockerfile
Normal file
11
docker/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
||||
FROM ekidd/rust-musl-builder as builder
|
||||
ADD --chown=rust:rust . ./
|
||||
|
||||
RUN cargo build --release
|
||||
|
||||
FROM alpine:latest
|
||||
COPY --from=builder /home/rust/src/target/x86_64-unknown-linux-musl/release/github-release-bot /opt/github-release-bot
|
||||
COPY ./docker/entrypoint.sh /opt/entrypoint.sh
|
||||
RUN chmod +x /opt/entrypoint.sh
|
||||
WORKDIR /opt
|
||||
ENTRYPOINT ["/opt/entrypoint.sh"]
|
20
docker/entrypoint.sh
Normal file
20
docker/entrypoint.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [[ -z "${TOKEN}" ]]; then
|
||||
echo "Must set Telegram token"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${CHATID}" ]]; then
|
||||
echo "Must set Telegram chat id"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INTERVAL="${INTERVAL:-28800}"
|
||||
SPREAD="${SPREAD:-600}"
|
||||
CONFIG="${CONFIG:-config.yaml}"
|
||||
DATADIR="${DATADIR:-data}"
|
||||
|
||||
/opt/github-release-bot --token ${TOKEN} --chatid ${CHATID} --interval ${INTERVAL} --spread ${SPREAD} --config ${CONFIG} --datadir ${DATADIR}
|
||||
|
164
src/main.rs
Normal file
164
src/main.rs
Normal file
@ -0,0 +1,164 @@
|
||||
use serde_yaml;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use octocrab;
|
||||
use telegram_bot::*;
|
||||
use clap::Clap;
|
||||
use std::future::Future;
|
||||
use std::time::Duration;
|
||||
use futures;
|
||||
use tokio;
|
||||
use log::*;
|
||||
use chrono::prelude::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
struct Repo {
|
||||
owner: String,
|
||||
repo: String
|
||||
}
|
||||
|
||||
impl Repo {
|
||||
async fn init(&mut self) {
|
||||
tokio::time::sleep(Duration::from_millis(0)).await;
|
||||
let _ = create_repo_files(&self.owner, &self.repo).await;
|
||||
}
|
||||
async fn resolve(&mut self) {
|
||||
let opts: Opts = Opts::parse();
|
||||
let random_spred = rand::random::<u64>();
|
||||
println!("{} - LOG - {}/{} will be checked in {} secs ({} interval and {} random spread)",
|
||||
Local::now().trunc_subsecs(0).to_rfc3339(), &self.owner, &self.repo, opts.interval+(random_spred % opts.spread), opts.interval, random_spred % opts.spread);
|
||||
tokio::time::sleep(Duration::from_secs(opts.interval+(random_spred % opts.spread))).await;
|
||||
let _res = github_get_latest_release(&self.owner, &self.repo).await;
|
||||
}
|
||||
|
||||
// fn print_result(&self) {
|
||||
// println!("{}, {}", self.owner, self.repo);
|
||||
// }
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
// #[clap(version = "1.0")]
|
||||
struct Opts {
|
||||
/// Config file
|
||||
#[clap(long, default_value = "config.yaml")]
|
||||
config: String,
|
||||
/// Data dir
|
||||
#[clap(long, default_value = "data")]
|
||||
datadir: String,
|
||||
/// Telegram token
|
||||
#[clap(long)]
|
||||
token: String,
|
||||
/// Check interval
|
||||
#[clap(long, default_value = "28800")]
|
||||
interval: u64,
|
||||
/// Spread interval
|
||||
#[clap(long, default_value = "600")]
|
||||
spread: u64,
|
||||
/// Telegram chat id
|
||||
#[clap(long, allow_hyphen_values(true))]
|
||||
chatid: i64,
|
||||
/// Verbose level, accept multiple occurrences
|
||||
#[clap(short, long, parse(from_occurrences))]
|
||||
verbose: usize,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let opts: Opts = Opts::parse();
|
||||
stderrlog::new()
|
||||
.module(module_path!())
|
||||
.verbosity(opts.verbose+1)
|
||||
.timestamp(stderrlog::Timestamp::Second)
|
||||
.quiet(false)
|
||||
.init()
|
||||
.unwrap();
|
||||
println!("{} - LOG - Config file name: {}", Local::now().trunc_subsecs(0).to_rfc3339(), opts.config);
|
||||
debug!("Using token: {}", opts.token);
|
||||
println!("{} - LOG - Using chat id: {}", Local::now().trunc_subsecs(0).to_rfc3339(), opts.chatid);
|
||||
println!("{} - LOG - Using check interval {} secs + random spread from {}", Local::now().trunc_subsecs(0).to_rfc3339(), opts.interval, opts.spread);
|
||||
|
||||
let config: Vec<Repo> = get_config(&opts.config).await?;
|
||||
let items: Vec<_> = config.iter().map(|x| Repo { owner: x.owner.trim().to_string(), repo: x.repo.trim().to_string() }).collect();
|
||||
|
||||
let _items = join_parallel(items.into_iter().map(|mut item| async {
|
||||
item.init().await;
|
||||
item
|
||||
}))
|
||||
.await;
|
||||
|
||||
loop {
|
||||
let items: Vec<_> = config.iter().map(|x| Repo { owner: x.owner.trim().to_string(), repo: x.repo.trim().to_string() }).collect();
|
||||
let _items = join_parallel(items.into_iter().map(|mut item| async {
|
||||
item.resolve().await;
|
||||
item
|
||||
}))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn join_parallel<T: Send + 'static>(
|
||||
futs: impl IntoIterator<Item = impl Future<Output = T> + Send + 'static>,
|
||||
) -> Vec<T> {
|
||||
let tasks: Vec<_> = futs.into_iter().map(tokio::spawn).collect();
|
||||
futures::future::join_all(tasks)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(Result::unwrap)
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn get_config(filename: impl AsRef<std::path::Path>) -> Result<Vec<Repo>, Box<dyn std::error::Error>> {
|
||||
let config = std::fs::File::open(filename)?;
|
||||
let contents: Vec<Repo> = serde_yaml::from_reader(config)?;
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
async fn create_repo_files(owner: &String, repo: &String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
debug!("Create repo file {}-{}", owner, repo);
|
||||
let opts: Opts = Opts::parse();
|
||||
let _repofile = tokio::fs::OpenOptions::new().write(true).create(true).open(opts.datadir.to_string() + "/" + &owner.trim().to_string() + "-" + &repo.trim().to_string()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read_file(filename: impl AsRef<std::path::Path>) -> Result<String, Box<dyn std::error::Error>>{
|
||||
let contents = tokio::fs::read_to_string(filename).await?;
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
async fn overwrite_repo_file(filename: impl AsRef<std::path::Path>, release_tag: &String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut file = tokio::fs::File::create(filename).await?;
|
||||
tokio::io::AsyncWriteExt::write_all(&mut file, release_tag.as_bytes()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
async fn telegram_notify(_release_tag: &String, release_url: &String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let opts: Opts = Opts::parse();
|
||||
let api = Api::new(opts.token);
|
||||
let chat = ChatId::new(opts.chatid);
|
||||
api.send(chat.text(release_url)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn github_get_latest_release(owner: &String, repo: &String) -> octocrab::Result<(), Box<dyn std::error::Error>> {
|
||||
let opts: Opts = Opts::parse();
|
||||
let release = octocrab::instance()
|
||||
.repos(owner, repo)
|
||||
.releases()
|
||||
.get_latest()
|
||||
.await?;
|
||||
|
||||
let version_in_file = read_file(opts.datadir.to_string() + "/" + &owner.trim().to_string() + "-" + &repo.trim().to_string()).await?;
|
||||
match version_in_file.to_string() == release.tag_name.to_string() {
|
||||
true => {
|
||||
info!("{}/{}: current known version is {:?}", owner, repo, version_in_file.to_string());
|
||||
println!("{} - LOG - {}/{}: known version matches with latest release {}", Local::now().trunc_subsecs(0).to_rfc3339(), owner, repo, release.tag_name.to_string());
|
||||
}
|
||||
_ => {
|
||||
info!("{}/{}: current known version is {:?}", owner, repo, version_in_file.to_string());
|
||||
println!("{} - LOG - {}/{}: known version does not match with latest release {}. Sending new release URL {}", Local::now().trunc_subsecs(0).to_rfc3339(), owner, repo, release.tag_name.to_string(), release.html_url);
|
||||
let _ = overwrite_repo_file(opts.datadir.to_string() + "/" + &owner.to_string() + "-" + &repo.to_string(), &release.tag_name.to_string()).await?;
|
||||
let _ = telegram_notify(&release.tag_name.to_string(), &release.html_url.to_string()).await?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user