mirror of https://github.com/MedzikUser/imgurs
Compare commits
218 Commits
Author | SHA1 | Date |
---|---|---|
M3DZIK | b6ef4241ca | |
M3DZIK | 6d6cbbd4b0 | |
M3DZIK | 723bf26dbd | |
M3DZIK | 56cbd96053 | |
Andre Julius | af0864d272 | |
M3DZIK | e782617f08 | |
dependabot[bot] | 7062046679 | |
dependabot[bot] | 9ba2233734 | |
dependabot[bot] | 044fa6525d | |
dependabot[bot] | f6d8e906fb | |
dependabot[bot] | 47b6225280 | |
Andre Julius | e9a7108f90 | |
renovate[bot] | 0398f7f675 | |
renovate[bot] | 300fa61a65 | |
renovate[bot] | 0ad55015ac | |
renovate[bot] | 965c4407f1 | |
renovate[bot] | 80292730f3 | |
MedzikUser | 304034f037 | |
Andre Julius | b6e715accf | |
renovate[bot] | d29b299a4b | |
renovate[bot] | c08e640421 | |
MedzikUser | 60119be30b | |
MedzikUser | 986a3b365c | |
MedzikUser | 25cc96774c | |
renovate[bot] | 111fef82d1 | |
renovate[bot] | 5d5beaecd8 | |
Andre Julius | 89a7969353 | |
MedzikUser | 25190ba5c1 | |
MedzikUser | 012784a352 | |
MedzikUser | 1bd632e906 | |
MedzikUser | a66fea840a | |
dependabot[bot] | dd44c5f041 | |
MedzikUser | 30a79bc8bb | |
MedzikUser | 2ac3424338 | |
MedzikUser | ecb5855e75 | |
MedzikUser | 31c737689c | |
MedzikUser | 8ddfeb3c19 | |
renovate[bot] | f5c70e4f4f | |
MedzikUser | c6b6bd1af7 | |
MedzikUser | 312d9cce19 | |
MedzikUser | ad117bf3ec | |
renovate[bot] | be73023511 | |
renovate[bot] | 0a8d4afb42 | |
renovate[bot] | bd91fb330c | |
renovate[bot] | 066cda0539 | |
renovate[bot] | b0c4dab74d | |
renovate[bot] | ac1cfc9535 | |
MedzikUser | f26170bc12 | |
MedzikUser | 71d124f435 | |
renovate[bot] | c75cad024f | |
renovate[bot] | 0eea52da89 | |
renovate[bot] | 54eea0f968 | |
renovate[bot] | 69211e9720 | |
renovate[bot] | 1d6aa51df6 | |
renovate[bot] | cfa28a2660 | |
renovate[bot] | ba494c594a | |
MedzikUser | 7081367bae | |
renovate[bot] | cc2dde74cd | |
renovate[bot] | 78dcb79b97 | |
renovate[bot] | 78c1c27332 | |
renovate[bot] | 13d9c82fcd | |
MedzikUser | 3c5507adaa | |
MedzikUser | 0bc00599e9 | |
MedzikUser | 2d1f1a4001 | |
Renovate Bot | 66551e77ad | |
Renovate Bot | d0e5367414 | |
Renovate Bot | 7446b0b420 | |
Renovate Bot | a1ca4dad9c | |
Renovate Bot | 7567d26b5b | |
Renovate Bot | feea8f8f2e | |
MedzikUser | 3f2af4f6f9 | |
MedzikUser | b396ccce19 | |
MedzikUser | 80dcd27bc1 | |
MedzikUser | 8bbee811d2 | |
MedzikUser | 831f816447 | |
Renovate Bot | 6da2dc252f | |
Renovate Bot | f935a3dddc | |
Renovate Bot | 15aa471d4f | |
MedzikUser | 9538ca5be8 | |
Renovate Bot | 5d3f5ed27b | |
MedzikUser | aac9819bc5 | |
MedzikUser | fb72dc1112 | |
MedzikUser | f4e0044678 | |
MedzikUser | 6f9559c6d8 | |
MedzikUser | 9b7705ed26 | |
MedzikUser | 58acb3df50 | |
MedzikUser | 70ffc05fee | |
MedzikUser | cca523f2b5 | |
MedzikUser | 18dfdb88a4 | |
MedzikUser | aabbea4182 | |
Renovate Bot | 20f0039d08 | |
Renovate Bot | 4e985ac39b | |
Renovate Bot | 8eeaa4dde6 | |
Renovate Bot | b7be76debf | |
Renovate Bot | 771d4e8dcc | |
Renovate Bot | 415e6c5735 | |
Renovate Bot | d73dc16909 | |
Renovate Bot | e5f91d7261 | |
Renovate Bot | 424175ed21 | |
Renovate Bot | 848767763e | |
Renovate Bot | be4c05a4da | |
Renovate Bot | 4fde33bad8 | |
Renovate Bot | c2adcc1f32 | |
Renovate Bot | 24d91f6151 | |
Renovate Bot | 1916df1bc0 | |
Renovate Bot | 931929dacb | |
Renovate Bot | d46a52d906 | |
Renovate Bot | 6c13b844ca | |
Renovate Bot | ccc120433a | |
Renovate Bot | 56cc3e30a1 | |
Renovate Bot | b024a5056e | |
Renovate Bot | fc154fe83d | |
Renovate Bot | 250a216b94 | |
Renovate Bot | 7966fb5553 | |
Renovate Bot | 939d267c28 | |
Renovate Bot | b9186a31c5 | |
Renovate Bot | 4b224d1331 | |
MedzikUser | 8b1f394575 | |
MedzikUser | 1048ea298f | |
MedzikUser | 8cd1a31b72 | |
MedzikUser | c3718579db | |
MedzikUser | b0e871cbf6 | |
MedzikUser | d98208f677 | |
MedzikUser | 08c25212ae | |
MedzikUser | b1bfe52a4c | |
Renovate Bot | b83847ff20 | |
Renovate Bot | 91746ad99c | |
Renovate Bot | b4cc9d5940 | |
Renovate Bot | a5f25caac2 | |
Renovate Bot | 89da666def | |
Renovate Bot | dbeefd9430 | |
Renovate Bot | 5477bd2ef6 | |
MedzikUser | 506aad97dc | |
MedzikUser | aa59201da7 | |
Renovate Bot | 03f57025e8 | |
MedzikUser | 09e7418658 | |
MedzikUser | 2451f47fa8 | |
MedzikUser | 9f22754fc3 | |
MedzikUser | 60b0dc0d84 | |
MedzikUser | 9b813457e7 | |
MedzikUser | 0cf1ad449e | |
MedzikUser | dba1dbae01 | |
Renovate Bot | f4f92b2713 | |
MedzikUser | 52cceb1ec1 | |
MedzikUser | 8a25a33110 | |
MedzikUser | a1c29a08c1 | |
MedzikUser | 6f29b8682e | |
MedzikUser | bf796f3358 | |
MedzikUser | b9c0daab88 | |
MedzikUser | ef1f9342ee | |
MedzikUser | 06a908ff36 | |
MedzikUser | 160c3b0b2f | |
renovate[bot] | 94d80c25af | |
MedzikUser | 6abb99d50a | |
MedzikUser | 572899509f | |
MedzikUser | 6ec952b60f | |
MedzikUser | e1164e1147 | |
MedzikUser | 6c7c334ede | |
MedzikUser | adc52ba207 | |
MedzikUser | 22bde2819e | |
MedzikUser | ee66bc93c4 | |
MedzikUser | a591d427f3 | |
MedzikUser | 4ad662e35e | |
Renovate Bot | a29ca66e0d | |
renovate[bot] | a177602edd | |
Medzik | d59b7240b3 | |
Medzik | f0a52fa512 | |
Medzik | e129ab0d65 | |
MedzikUserBot | 24d7be84fa | |
Medzik | b7c98934e6 | |
MedzikUserBot | 7598ba5293 | |
Medzik | 461b53d681 | |
MedzikUserBot | 2c22737070 | |
Medzik | a4a17a4fc5 | |
Medzik | 329263edb0 | |
Renovate Bot | f516bde8cd | |
Renovate Bot | fd12defe14 | |
Renovate Bot | 5f182560c5 | |
Renovate Bot | f065eb6402 | |
Renovate Bot | 36c74a4a3f | |
Renovate Bot | 56323eee26 | |
Renovate Bot | 572ff08d03 | |
Medzik | e8e8d498f7 | |
Medzik | a0192d997d | |
MedzikUserBot | d272e3435d | |
Medzik | 39f1debdd2 | |
Oskar | 86397d5e23 | |
MedzikUser | 1cbcc8a92b | |
MedzikUser | a42972c65f | |
MedzikUser | 6d01fe6112 | |
MedzikUser | 7455932107 | |
MedzikUser | ecace5aa46 | |
MedzikUser | 979490e9f0 | |
MedzikUser | bfb8db96e1 | |
MedzikUser | 5a620ea1c2 | |
MedzikUser | 62c0352345 | |
MedzikUser | 60e7ba5bec | |
MedzikUser | b1b3b18b7a | |
MedzikUser | d421835c54 | |
MedzikUser | 03970a517a | |
MedzikUser | e7fab7de2d | |
MedzikUserBot | b804e8faa2 | |
MedzikUser | 62531904f9 | |
MedzikUserBot | bd3ef20a7f | |
MedzikUser | a83788d0c6 | |
MedzikUser | 37f51f97dd | |
MedzikUser | 605e051cdb | |
MedzikUserBot | 4fc60d8bbe | |
MedzikUser | 5edeac60fd | |
MedzikUserBot | af6830da0c | |
MedzikUser | 2d38cf7e28 | |
MedzikUserBot | 076d7e04c0 | |
MedzikUser | ee7293791c | |
MedzikUser | 93ffaaed5d | |
MedzikUser | c2a2c20de2 | |
MedzikUser | 754874388a | |
MedzikUser | 3bd2a67198 | |
MedzikUser | be0452644b |
|
@ -1,20 +0,0 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = false
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = false
|
|
@ -1,6 +1,14 @@
|
|||
name: Build release binaries (and publish them if this is a tag)
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
binaries:
|
||||
|
@ -9,18 +17,31 @@ jobs:
|
|||
|
||||
matrix:
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
- aarch64-unknown-linux-musl
|
||||
- x86_64-pc-windows-msvc
|
||||
- x86_64-apple-darwin
|
||||
- aarch64-apple-darwin
|
||||
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifact_name: target/x86_64-unknown-linux-gnu/release/imgurs
|
||||
release_name: x86_64-unknown-linux-gnu
|
||||
cross: false
|
||||
target: x86_64-unknown-linux-musl
|
||||
artifact_name: target/x86_64-unknown-linux-musl/release/imgurs
|
||||
release_name: x86_64-unknown-linux-musl
|
||||
cross: true
|
||||
strip: true
|
||||
compress: true
|
||||
cargo_flags: ""
|
||||
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-musl
|
||||
artifact_name: target/aarch64-unknown-linux-musl/release/imgurs
|
||||
release_name: aarch64-unknown-linux-musl
|
||||
cross: true
|
||||
strip: false
|
||||
compress: true
|
||||
cargo_flags: ""
|
||||
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
artifact_name: target/x86_64-pc-windows-msvc/release/imgurs.exe
|
||||
|
@ -29,6 +50,7 @@ jobs:
|
|||
strip: true
|
||||
compress: true
|
||||
cargo_flags: ""
|
||||
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
artifact_name: target/x86_64-apple-darwin/release/imgurs
|
||||
|
@ -38,6 +60,15 @@ jobs:
|
|||
compress: true
|
||||
cargo_flags: ""
|
||||
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
artifact_name: target/aarch64-apple-darwin/release/imgurs
|
||||
release_name: aarch64-apple-darwin
|
||||
cross: false
|
||||
strip: true
|
||||
compress: true
|
||||
cargo_flags: ""
|
||||
|
||||
name: ${{ matrix.os }} for ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
@ -55,7 +86,7 @@ jobs:
|
|||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --target=${{ matrix.target }} ${{ matrix.cargo_flags }}
|
||||
args: --all --release --target=${{ matrix.target }} ${{ matrix.cargo_flags }}
|
||||
use-cross: ${{ matrix.cross }}
|
||||
|
||||
- name: Compress binaries
|
||||
|
@ -67,7 +98,7 @@ jobs:
|
|||
if: ${{ matrix.compress }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: ${{ matrix.artifact_name }}
|
||||
|
@ -97,6 +128,6 @@ jobs:
|
|||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ matrix.artifact_name }}
|
||||
tag: ${{ github.ref }}
|
||||
asset_name: miniserve-$tag-${{ matrix.release_name }}
|
||||
asset_name: imgurs-$tag-${{ matrix.release_name }}
|
||||
body: ${{ steps.changelog_reader.outputs.log_entry }}
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
|
|
@ -2,7 +2,13 @@ name: Rust
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches:
|
||||
- main
|
||||
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
@ -19,23 +25,31 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: cargo build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
- name: cargo build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all
|
||||
|
||||
- name: cargo clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
- name: cargo test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all-features
|
||||
|
||||
- name: cargo clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings --no-deps
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
name: Rust fmt
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup GIT
|
||||
run: |
|
||||
git config --global user.name "MedzikUserBot"
|
||||
git config --global user.email "rm99iv9s@duck.com"
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: cargo fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git add .
|
||||
git diff-index --quiet HEAD || git commit -m "rustfmt"
|
||||
|
||||
- name: Push changes
|
||||
uses: ad-m/github-push-action@b007e7b818e33b04afd056e4c4b57ba917145d7a
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: 'main'
|
|
@ -1 +1,7 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target
|
||||
|
||||
# IDE configs
|
||||
.vscode
|
||||
.idea
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
<!-- next-header -->
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.11.3] - 2024-04-11
|
||||
- Updated dependencies
|
||||
- Replaced url validation with url crate in imgurs-cli
|
||||
|
||||
## [0.11.2] - 2023-06-22
|
||||
- Updated dependencies
|
||||
- Fix deprecation warning of base64 crate.
|
||||
|
||||
## [0.11.1] - 2022-12-11
|
||||
### Fixed
|
||||
- `album_title` can be null, `account_id` can be null, string or number #92, thanks to @NotNorom
|
||||
|
||||
### Changed
|
||||
- Updated dependencies
|
||||
|
||||
## [0.11.0] - 2022-11-07
|
||||
### Added
|
||||
- Added `with_http_client` method to ImgurClient #87, thanks to @NotNorom
|
||||
|
||||
### Changed
|
||||
- Updated crate arboard to v3
|
||||
- Updated crate simple_logger to v4
|
||||
|
||||
## [0.10.0] - 2022-10-01
|
||||
- add configuration for tls (rustls-tls or native-tls)
|
||||
|
||||
## [0.9.1] - 2022-09-22
|
||||
- delete debug info from cli in release build
|
||||
|
||||
## [0.9.0] - 2022-09-05
|
||||
- moved cli to other crate
|
||||
- added get_album function
|
||||
|
||||
## [0.8.1] - 2022-06-18
|
||||
- fix tests
|
||||
- add missing doc
|
||||
- use `serde` instead of `serde_derive`
|
||||
|
||||
## [0.8.0] - 2022-06-13
|
||||
- add custom `Error` type
|
||||
- move api requests to `requests/` mod
|
||||
- comment code
|
||||
|
||||
## [0.7.4] - 2022-05-18
|
||||
### HOTFIX
|
||||
- fixed built on macos and windows
|
||||
|
||||
## [0.7.3] - 2022-05-18
|
||||
### Library
|
||||
- add code comments and tests
|
||||
- change `String` to `&str` in ImgurClient functions
|
||||
|
||||
### Other
|
||||
- bump deps
|
||||
- use `anyhow::Result<...>` instead `Result<..., Error>`
|
||||
|
||||
## [0.7.2] - 2022-04-05
|
||||
### HotFix
|
||||
- fix upload image from file
|
||||
|
||||
## [0.7.1] - 2022-04-04
|
||||
- fix build on what is not linux
|
||||
|
||||
## [0.7.0] - 2022-04-03
|
||||
### CLI
|
||||
- completions: changed type from String to Shell
|
||||
- removed `&` from `cli.commands` (line 54 in [parse.rs](./src/cli/parse.rs))
|
||||
|
||||
### Library
|
||||
- removed `.map_err(anyhow::Error::new)` when function returns error
|
||||
|
||||
### Added
|
||||
- commands in the code
|
||||
- api functions to `impl` in `ImgurClient`
|
||||
- documentation (example usage)
|
||||
|
||||
### Breaking Changes
|
||||
- lib: moved everything to the main package with api submodules (before `imgurs::api::ImgurClient`, after `imgurs::api::ImgurClient`)
|
||||
|
||||
## [0.6.0] - 2022-03-14
|
||||
### CLI
|
||||
- webhook: added url in title
|
||||
- cli: change image domain to your own (set in config)
|
||||
- if the configuration file cannot be open, ask the user whether to overwrite the file instead of overwriting it without asking
|
||||
- logger: set `max_level_debug` in debug binary
|
||||
|
||||
## [0.5.1] - 2022-03-08
|
||||
### Cli
|
||||
- change webhook to discord-webhook (to use rustls)
|
||||
|
||||
## [0.5.0] - 2022-03-07
|
||||
### CLI
|
||||
- clipboard: add support for xclip and termux
|
||||
- webhook: send webhook to discord if image uploaded ([example](https://i.imgur.com/CPpHEec.png))
|
||||
|
||||
### Library
|
||||
- if body length is greater than 30, return message `body is too length`
|
||||
|
||||
## [0.4.0] - 2022-02-27
|
||||
### CLI
|
||||
- update logger
|
||||
- added clipboard
|
||||
- added manpage
|
||||
- added completion for elvish
|
||||
- if failed to upload image send notify with error message
|
||||
|
||||
### Library
|
||||
- added Clone derive
|
||||
- if body length is > 30 return body is too length
|
||||
|
||||
## [0.3.0] - 2022-01-28
|
||||
### CLI
|
||||
- SimpleLogger init error handling
|
||||
- better panic
|
||||
- panic instead of send log error
|
||||
- add url validate
|
||||
|
||||
### Library
|
||||
- The returned error in the Result is from now on anyhow::Error and not String.
|
||||
- Do not exit program if send_api_request error
|
||||
- rename ImgurHandle -> ImgurClient
|
||||
|
||||
## [0.2.0] - 2022-01-23
|
||||
### Added
|
||||
#### CLI
|
||||
- create default config, if not exits
|
||||
- when the image uploaded, send a notification (can be turn off in config)
|
||||
- shell completions
|
||||
|
||||
#### Library
|
||||
- change OpenSSL to RusTLS
|
||||
- move api request to fn send_api_request
|
||||
|
||||
### Fixed
|
||||
- api rate limit (error decoding response body: invalid value: integer \`200\`, expected i8 at line 1 column 140)
|
||||
|
||||
## [0.1.0] - 2022-01-23
|
||||
### CLI
|
||||
- commands
|
||||
- credits
|
||||
- delete
|
||||
- info
|
||||
- upload
|
||||
- toml config parser
|
||||
|
||||
### Library
|
||||
- image info
|
||||
- rate limit
|
||||
- image info
|
||||
- delete image
|
||||
- upload image
|
||||
|
||||
<!-- next-url -->
|
||||
[Unreleased]: https://github.com/MedzikUser/imgurs/compare/v0.11.3...HEAD
|
||||
[0.11.3]: https://github.com/MedzikUser/imgurs/commits/v0.11.3
|
||||
[0.11.2]: https://github.com/MedzikUser/imgurs/commits/v0.11.2
|
||||
[0.11.1]: https://github.com/MedzikUser/imgurs/commits/v0.11.1
|
||||
[0.11.0]: https://github.com/MedzikUser/imgurs/commits/v0.11.0
|
||||
[0.10.0]: https://github.com/MedzikUser/imgurs/commits/v0.10.0
|
||||
[0.9.1]: https://github.com/MedzikUser/imgurs/commits/v0.9.1
|
||||
[0.9.0]: https://github.com/MedzikUser/imgurs/commits/v0.9.0
|
||||
[0.8.1]: https://github.com/MedzikUser/imgurs/commits/v0.8.1
|
||||
[0.8.0]: https://github.com/MedzikUser/imgurs/commits/v0.8.0
|
||||
[0.7.4]: https://github.com/MedzikUser/imgurs/commits/v0.7.4
|
||||
[0.7.3]: https://github.com/MedzikUser/imgurs/commits/v0.7.3
|
||||
[0.7.2]: https://github.com/MedzikUser/imgurs/commits/v0.7.2
|
||||
[0.7.1]: https://github.com/MedzikUser/imgurs/commits/v0.7.1
|
||||
[0.7.0]: https://github.com/MedzikUser/imgurs/commits/v0.7.0
|
||||
[0.6.0]: https://github.com/MedzikUser/imgurs/commits/v0.6.0
|
||||
[0.5.1]: https://github.com/MedzikUser/imgurs/commits/v0.5.1
|
||||
[0.5.0]: https://github.com/MedzikUser/imgurs/commits/v0.5.0
|
||||
[0.4.0]: https://github.com/MedzikUser/imgurs/commits/v0.4.0
|
||||
[0.3.0]: https://github.com/MedzikUser/imgurs/commits/v0.3.0
|
||||
[0.2.0]: https://github.com/MedzikUser/imgurs/commits/v0.2.0
|
||||
[0.1.0]: https://github.com/MedzikUser/imgurs/commits/v0.1.0
|
File diff suppressed because it is too large
Load Diff
65
Cargo.toml
65
Cargo.toml
|
@ -1,36 +1,43 @@
|
|||
[workspace]
|
||||
members = ["imgurs-cli"]
|
||||
resolver = "2"
|
||||
|
||||
[package]
|
||||
name = "imgurs"
|
||||
version = "0.1.0"
|
||||
version = "0.11.3"
|
||||
description = "API for Imgur"
|
||||
license = "BSD-3-Clause"
|
||||
authors = ["M3DZIK <me@medzik.dev>"]
|
||||
homepage = "https://github.com/M3DZIK/imgurs"
|
||||
repository = "https://github.com/M3DZIK/imgurs.git"
|
||||
keywords = ["imgur", "imgur-api", "image", "image-upload"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["imgur", "rustls-tls"]
|
||||
full = ["imgur"]
|
||||
rustls-tls = ["reqwest/rustls-tls"]
|
||||
native-tls = ["reqwest/native-tls"]
|
||||
imgur = []
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 'z'
|
||||
codegen-units = 1
|
||||
|
||||
[dependencies]
|
||||
dirs = "4.0.0"
|
||||
serde = "1.0.134"
|
||||
serde_derive = "1.0.134"
|
||||
toml = "0.5.8"
|
||||
serde_json = "1.0.75"
|
||||
chrono = "0.4.19"
|
||||
base64 = "0.13.0"
|
||||
# HTTP
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "multipart"] }
|
||||
# Request
|
||||
base64 = "0.22"
|
||||
url = "2.5.0" # validate url address
|
||||
# Response
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "3.0.7"
|
||||
features = ["derive", "cargo", "unicode"]
|
||||
# Errors
|
||||
thiserror = "1.0"
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4.14"
|
||||
features = ["release_max_level_info"]
|
||||
|
||||
[dependencies.simple_logger]
|
||||
version = "2.1.0"
|
||||
default-features = false
|
||||
features = ["colors"]
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.11.9"
|
||||
features = ["json"]
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.15.0"
|
||||
features = ["full"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dev-dependencies]
|
||||
# Async tests
|
||||
tokio = { version = "1.37", features = ["macros", "rt-multi-thread"] }
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
[crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
|
||||
[docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
|
||||
|
||||
# Imgurs - CLI and Library for Imgur API
|
||||
|
||||
[![crates-io]](https://crates.io/crates/imgurs)
|
||||
[![docs-rs]](https://docs.rs/imgurs)
|
||||
|
||||
## Screenshots
|
||||
|
||||
![](https://i.imgur.com/MG35kvf.png)
|
||||
|
||||
![](https://i.imgur.com/TSxBrhO.png)
|
||||
|
||||
## Shell completions
|
||||
|
||||
Here are some examples of usage
|
||||
```bash
|
||||
# For bash
|
||||
imgurs completions bash > ~/.local/share/bash-completion/completions/imgurs
|
||||
# For zsh
|
||||
imgurs completions zsh > /usr/local/share/zsh/site-functions/_imgurs
|
||||
# For fish
|
||||
imgurs completions fish > ~/.config/fish/completions/imgurs.fish
|
||||
```
|
||||
|
||||
## Man page
|
||||
|
||||
Generate manpage
|
||||
|
||||
imgurs manpage | gzip > /usr/share/man/man1/imgurs.1.gz
|
||||
|
||||
## Dependencies
|
||||
- support clipboard on Linux
|
||||
- **xsel**
|
||||
- **xclip** - alternative to **xsel**
|
||||
- **termux-api** - on **Termux**
|
||||
- **libnotify** - support notification on Linux
|
||||
|
||||
## How to install Imgurs CLI?
|
||||
|
||||
### **Linux**
|
||||
Download imgurs-linux from [the releases page](https://github.com/MedzikUser/imgurs/releases/latest) and run
|
||||
|
||||
chmod +x imgurs-linux
|
||||
./imgurs-linux
|
||||
|
||||
#### **Arch Linux**
|
||||
Using yay ([AUR](https://aur.archlinux.org/packages/imgurs))
|
||||
|
||||
yay -S imgurs
|
||||
|
||||
or can add [this repo](https://github.com/archlinux-pkg/packages) and run
|
||||
|
||||
sudo pacman -Sy imgurs
|
||||
|
||||
### **OSX**
|
||||
Download imgurs-darwin from [the releases page](https://github.com/MedzikUser/imgurs/releases/latest) and run
|
||||
|
||||
chmod +x imgurs-darwin
|
||||
./imgurs-darwin
|
||||
|
||||
### **Windows**
|
||||
Download imgurs-windows.exe from [the releases page](https://github.com/MedzikUser/imgurs/releases/latest) and run
|
||||
|
||||
imgurs-windows.exe
|
||||
|
||||
### **Compile with Cargo**
|
||||
Make sure you have a recent version of Rust. Then you can run
|
||||
|
||||
cargo install imgurs-cli
|
|
@ -0,0 +1,9 @@
|
|||
[imgur]
|
||||
id = '3e3ce0d7ac14d56'
|
||||
image_cdn = 'i.imgur.com'
|
||||
|
||||
[notification]
|
||||
enabled = true
|
||||
|
||||
[clipboard]
|
||||
enabled = true
|
|
@ -0,0 +1,46 @@
|
|||
[package]
|
||||
name = "imgurs-cli"
|
||||
version = "0.11.3"
|
||||
description = "CLI for Imgur"
|
||||
license = "BSD-3-Clause"
|
||||
authors = ["M3DZIK <me@medzik.dev>"]
|
||||
homepage = "https://github.com/M3DZIK/imgurs"
|
||||
repository = "https://github.com/M3DZIK/imgurs.git"
|
||||
keywords = ["imgur", "imgur-api", "image", "image-upload"]
|
||||
categories = ["command-line-utilities"]
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "imgurs"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
# Async runtime
|
||||
tokio = { version = "1.37", features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
# CLI
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
clap_complete = "4.5"
|
||||
clap_mangen = "0.2"
|
||||
|
||||
# Errors
|
||||
anyhow = "1.0"
|
||||
|
||||
# Logger
|
||||
log = { version = "0.4", features = ["release_max_level_info"] }
|
||||
simple_logger = "4.3"
|
||||
colored = "2.1"
|
||||
|
||||
# Config
|
||||
toml = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
# Other
|
||||
chrono = "0.4" # parse upload date
|
||||
notify-rust = "4.11" # send notification after upload
|
||||
dirs = "5.0" # get system configuration directory
|
||||
|
||||
imgurs = { path = "..", version = "0.11.0", features = ["full"] }
|
||||
|
||||
[target.'cfg(not(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten")))))'.dependencies]
|
||||
arboard = "3.3" # copy url to clipboard
|
|
@ -0,0 +1 @@
|
|||
../config.toml
|
|
@ -0,0 +1,37 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod toml;
|
||||
|
||||
/// Configuration schema
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
/// Imgur API configuration options
|
||||
pub imgur: ConfigImgur,
|
||||
/// Notification options
|
||||
pub notification: ConfigNotification,
|
||||
/// Clipboard options
|
||||
pub clipboard: ConfigClipboard,
|
||||
}
|
||||
|
||||
/// Imgur API configuration options
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ConfigImgur {
|
||||
/// Imgur Client ID
|
||||
pub id: String,
|
||||
/// Imgur Domain (e.g. if you have a imgur proxy)
|
||||
pub image_cdn: String,
|
||||
}
|
||||
|
||||
/// Notification options
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ConfigNotification {
|
||||
/// Send notification
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
/// Clipboard options
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ConfigClipboard {
|
||||
/// Copy image url to clipboard
|
||||
pub enabled: bool,
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
use std::{
|
||||
fs::{create_dir_all, read_to_string, File},
|
||||
io::{self, Write as _},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use colored::Colorize;
|
||||
use dirs::config_dir;
|
||||
use log::warn;
|
||||
use toml::from_str as toml_from_str;
|
||||
|
||||
use super::Config;
|
||||
|
||||
/// Configuration file path (in system config directory).
|
||||
const CONFIG_DIR: &str = "/imgurs/config.toml";
|
||||
|
||||
/// Parse configuration file
|
||||
pub fn parse() -> Config {
|
||||
// parse config or use default
|
||||
toml().unwrap_or_else(|err| {
|
||||
let mut stdout = std::io::stdout();
|
||||
|
||||
write!(stdout, "{}", "The configuration file could not be opened. Do you want to create/overwrite with DEFAULT values? (Y/n): ".yellow()).unwrap();
|
||||
stdout.flush().unwrap();
|
||||
|
||||
let mut value = String::new();
|
||||
io::stdin()
|
||||
.read_line(&mut value)
|
||||
.expect("failed to read line");
|
||||
|
||||
if value.to_lowercase() != "n\n" {
|
||||
warn!("Parse toml config error: {err}! Creating config file...");
|
||||
|
||||
let default_config = include_str!(concat!("../../config.toml"));
|
||||
|
||||
let sys_config_dir = config_dir().expect("find config dir");
|
||||
let config_dir = format!("{}{CONFIG_DIR}", sys_config_dir.to_string_lossy());
|
||||
let config_path = Path::new(&config_dir);
|
||||
|
||||
let prefix = config_path.parent().unwrap();
|
||||
create_dir_all(prefix).expect("create config dir");
|
||||
|
||||
let mut file_ref = File::create(config_path).expect("create failed");
|
||||
|
||||
file_ref
|
||||
.write_all(default_config.as_bytes())
|
||||
.expect("failed write default config to file");
|
||||
|
||||
toml().expect("parse toml config")
|
||||
} else {
|
||||
panic!("Configuration file creation cancelled!")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn toml() -> anyhow::Result<Config> {
|
||||
let config_dir = config_dir().unwrap();
|
||||
let file_dir = format!("{}{CONFIG_DIR}", config_dir.to_string_lossy());
|
||||
let toml_str = read_to_string(file_dir)?;
|
||||
let decode = toml_from_str(&toml_str)?;
|
||||
|
||||
Ok(decode)
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
#[cfg(all(
|
||||
unix,
|
||||
not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))
|
||||
))]
|
||||
// use xclip (or a similar program that is installed) because the kernel deletes the clipboard after the process ends
|
||||
pub fn set_clipboard(content: &str) {
|
||||
fn is_program_in_path(program: &str) -> bool {
|
||||
if let Ok(path) = std::env::var("PATH") {
|
||||
for p in path.split(':') {
|
||||
let p_str = format!("{}/{}", p, program);
|
||||
if std::fs::metadata(p_str).is_ok() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
use std::{
|
||||
io::Write,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use colored::Colorize;
|
||||
|
||||
let mut child;
|
||||
|
||||
// xsel
|
||||
if is_program_in_path("xsel") {
|
||||
child = Command::new("xsel")
|
||||
.arg("--input")
|
||||
.arg("--clipboard")
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("execute command xsel")
|
||||
|
||||
// xclip
|
||||
} else if is_program_in_path("xclip") {
|
||||
child = Command::new("xclip")
|
||||
.arg("-in")
|
||||
.arg("-selection")
|
||||
.arg("clipboard")
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("execute command xclip")
|
||||
|
||||
// termux
|
||||
} else if is_program_in_path("termux-clipboard-set") {
|
||||
child = Command::new("termux-clipboard-set")
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("execute command termux-clipboard-set")
|
||||
|
||||
// the above programs responsible for the clipboard were not found
|
||||
} else {
|
||||
println!(
|
||||
"{} {}",
|
||||
"WARN".yellow(),
|
||||
"command for clipboard not found".magenta()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// copy the content (send it to stdin command)
|
||||
child
|
||||
.stdin
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.write_all(content.as_bytes())
|
||||
.expect("execute command");
|
||||
|
||||
child
|
||||
.wait_with_output()
|
||||
.expect("wait for clipboard command output");
|
||||
}
|
||||
|
||||
#[cfg(not(all(
|
||||
unix,
|
||||
not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))
|
||||
)))]
|
||||
pub fn set_clipboard(content: &str) {
|
||||
let mut clipboard = arboard::Clipboard::new().unwrap();
|
||||
clipboard.set_text(content.to_string()).unwrap();
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
use std::time::{Duration, UNIX_EPOCH};
|
||||
|
||||
use chrono::{prelude::DateTime, Utc};
|
||||
use colored::Colorize;
|
||||
use imgurs::ImgurClient;
|
||||
|
||||
pub async fn credits(client: ImgurClient) {
|
||||
// get client ratelimit from imgur api
|
||||
let i = client
|
||||
.rate_limit()
|
||||
.await
|
||||
.expect("send request to imgur api");
|
||||
|
||||
// format image upload date
|
||||
let date = UNIX_EPOCH + Duration::from_secs(i.data.user_reset.try_into().unwrap());
|
||||
let datetime = DateTime::<Utc>::from(date);
|
||||
let timestamp_str = datetime.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
println!(
|
||||
"{} {}",
|
||||
"user limit".green(),
|
||||
i.data.user_limit.to_string().magenta()
|
||||
);
|
||||
println!(
|
||||
"{} {}",
|
||||
"user remaining".green(),
|
||||
i.data.user_remaining.to_string().magenta()
|
||||
);
|
||||
println!(
|
||||
"{} {} {}",
|
||||
"user reset".green(),
|
||||
timestamp_str.magenta(),
|
||||
"(UTC)".blue()
|
||||
);
|
||||
println!(
|
||||
"{} {}",
|
||||
"client limit".green(),
|
||||
i.data.client_limit.to_string().magenta()
|
||||
);
|
||||
println!(
|
||||
"{} {}",
|
||||
"client remaining ".green(),
|
||||
i.data.client_remaining.to_string().magenta()
|
||||
);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
use colored::Colorize;
|
||||
use imgurs::ImgurClient;
|
||||
|
||||
pub async fn delete_image(client: ImgurClient, delete_hash: String) {
|
||||
// delete image from imgur
|
||||
client
|
||||
.delete_image(&delete_hash)
|
||||
.await
|
||||
.expect("send api request");
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
"If Delete Hash was correct the image was deleted!".magenta()
|
||||
);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
use imgurs::ImgurClient;
|
||||
|
||||
use super::print_image_info;
|
||||
|
||||
pub async fn image_info(client: ImgurClient, id: String) {
|
||||
// get a image info from imgur
|
||||
let info = client
|
||||
.image_info(&id)
|
||||
.await
|
||||
.expect("send request to imfur api");
|
||||
|
||||
// print image information from imgur
|
||||
print_image_info(&info);
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
mod clipboard;
|
||||
mod credits;
|
||||
mod delete_image;
|
||||
mod info_image;
|
||||
mod upload_image;
|
||||
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
|
||||
use chrono::{prelude::DateTime, Utc};
|
||||
use colored::Colorize;
|
||||
use imgurs::ImageInfo;
|
||||
|
||||
pub use self::{clipboard::*, credits::*, delete_image::*, info_image::*, upload_image::*};
|
||||
|
||||
// print image information from imgur
|
||||
pub fn print_image_info(i: &ImageInfo) {
|
||||
// format image upload date
|
||||
let d = UNIX_EPOCH + Duration::from_secs(i.data.datetime.try_into().unwrap());
|
||||
let datetime = DateTime::<Utc>::from(d);
|
||||
let timestamp_str = datetime.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
// image title
|
||||
if i.data.title != None {
|
||||
let title = i.data.title.clone();
|
||||
|
||||
println!(
|
||||
"{} {}",
|
||||
"title".green(),
|
||||
title.unwrap_or_else(|| "unknown".to_string()).magenta()
|
||||
);
|
||||
}
|
||||
|
||||
// image description
|
||||
if i.data.description != None {
|
||||
let desc = i.data.description.clone();
|
||||
|
||||
println!(
|
||||
"{} {}",
|
||||
"description".green(),
|
||||
desc.unwrap_or_else(|| "unknown".to_string()).magenta()
|
||||
);
|
||||
}
|
||||
|
||||
// image deletehas
|
||||
if i.data.deletehash != None {
|
||||
let delhash = i.data.deletehash.clone();
|
||||
|
||||
println!(
|
||||
"{} {}",
|
||||
"deletehash".green(),
|
||||
delhash.unwrap_or_else(|| "unknown".to_string()).magenta()
|
||||
);
|
||||
}
|
||||
|
||||
println!("{} {}", "id".green(), i.data.id.magenta());
|
||||
println!(
|
||||
"{} {} {}",
|
||||
"upload date".green(),
|
||||
timestamp_str.magenta(),
|
||||
"(UTC)".blue()
|
||||
);
|
||||
println!("{} {}", "type".green(), i.data.img_type.magenta());
|
||||
println!("{} {}", "width".green(), i.data.width.to_string().magenta());
|
||||
println!(
|
||||
"{} {}",
|
||||
"height".green(),
|
||||
i.data.height.to_string().magenta()
|
||||
);
|
||||
println!(
|
||||
"{} {} {}",
|
||||
"size".green(),
|
||||
(i.data.size / 1000).to_string().magenta(),
|
||||
"KB".blue()
|
||||
);
|
||||
println!("{} {}", "views".green(), i.data.views.to_string().magenta());
|
||||
println!(
|
||||
"{} {}",
|
||||
"bandwidth".green(),
|
||||
i.data.bandwidth.to_string().magenta()
|
||||
);
|
||||
println!("{} {}", "link".green(), i.data.link.magenta());
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
use imgurs::ImgurClient;
|
||||
use notify_rust::Notification;
|
||||
|
||||
use crate::{
|
||||
config::toml,
|
||||
imgur::{clipboard::set_clipboard, print_image_info},
|
||||
};
|
||||
|
||||
// show notification
|
||||
macro_rules! notify (
|
||||
($notification: expr) => (
|
||||
if toml::parse().notification.enabled {
|
||||
$notification.show().expect("send notification");
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
pub async fn upload_image(client: ImgurClient, path: String) {
|
||||
// parse configuration file
|
||||
let config = toml::parse();
|
||||
|
||||
// upload a image to imgur
|
||||
let mut i = client.upload_image(&path).await.unwrap_or_else(|err| {
|
||||
notify!(Notification::new()
|
||||
.summary("Error!")
|
||||
.body(&format!("Error: {}", err))
|
||||
.appname("Imgurs")); // I don't think you can set it to error
|
||||
|
||||
panic!("send request to imgur api: {}", err)
|
||||
});
|
||||
|
||||
// change domain to proxy (to be set in config)
|
||||
if config.imgur.image_cdn != "i.imgur.com" {
|
||||
i.data.link = i.data.link.replace("i.imgur.com", &config.imgur.image_cdn)
|
||||
}
|
||||
|
||||
// print image information from imgur
|
||||
print_image_info(&i);
|
||||
|
||||
// send notification that the image has been uploaded
|
||||
notify!(Notification::new()
|
||||
.summary("Imgurs")
|
||||
.body(&format!("Uploaded {}", i.data.link)));
|
||||
|
||||
// if enabled copy link to clipboard
|
||||
if config.clipboard.enabled {
|
||||
set_clipboard(&i.data.link)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
use std::io::stdout;
|
||||
|
||||
use clap::{Command, CommandFactory, Parser};
|
||||
use clap_complete::{generate, Generator, Shell};
|
||||
use imgurs::ImgurClient;
|
||||
use simple_logger::SimpleLogger;
|
||||
|
||||
use crate::imgur::*;
|
||||
|
||||
mod config;
|
||||
mod imgur;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
name = "imgurs",
|
||||
about = "Imgur API CLI",
|
||||
long_about = env!("CARGO_PKG_DESCRIPTION"),
|
||||
version = env!("CARGO_PKG_VERSION"),
|
||||
)]
|
||||
enum Cli {
|
||||
#[clap(about = "Print Client Rate Limit", display_order = 1)]
|
||||
Credits,
|
||||
|
||||
#[clap(about = "Upload image to Imgur", display_order = 2)]
|
||||
Upload { path: String },
|
||||
|
||||
#[clap(about = "Delete image from Imgur", display_order = 3)]
|
||||
Delete { delete_hash: String },
|
||||
|
||||
#[clap(about = "Print image info", display_order = 4)]
|
||||
Info { id: String },
|
||||
|
||||
#[clap(
|
||||
about = "Generate completion file for a shell [bash, elvish, fish, powershell, zsh]",
|
||||
display_order = 5
|
||||
)]
|
||||
Completions { shell: Shell },
|
||||
|
||||
#[clap(about = "Generate man page", display_order = 6)]
|
||||
Manpage,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
|
||||
// parse config file
|
||||
let config = config::toml::parse();
|
||||
|
||||
// create imgur client
|
||||
let client = ImgurClient::new(&config.imgur.id);
|
||||
|
||||
let args = Cli::parse();
|
||||
|
||||
match args {
|
||||
Cli::Credits => credits(client).await,
|
||||
|
||||
Cli::Upload { path } => upload_image(client, path).await,
|
||||
|
||||
Cli::Delete { delete_hash } => delete_image(client, delete_hash.to_string()).await,
|
||||
|
||||
Cli::Info { id } => image_info(client, id.to_string()).await,
|
||||
|
||||
Cli::Completions { shell } => {
|
||||
let mut app = Cli::command();
|
||||
|
||||
fn print_completions<G: Generator>(gen: G, app: &mut Command) {
|
||||
generate(gen, app, app.get_name().to_string(), &mut stdout())
|
||||
}
|
||||
|
||||
print_completions(shell, &mut app)
|
||||
},
|
||||
|
||||
Cli::Manpage => {
|
||||
let clap_app = Cli::command();
|
||||
let man = clap_mangen::Man::new(clap_app);
|
||||
|
||||
man.render(&mut stdout())
|
||||
.expect("Failed to generate man page");
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base",
|
||||
"schedule:weekly",
|
||||
"group:allNonMajor",
|
||||
":semanticCommits"
|
||||
],
|
||||
"labels": [
|
||||
"dependencies"
|
||||
],
|
||||
"automergeType": "pr",
|
||||
"prCreation": "immediate",
|
||||
"packageRules": [
|
||||
{
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch",
|
||||
"pin",
|
||||
"digest"
|
||||
],
|
||||
"automerge": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
# https://rust-lang.github.io/rustfmt
|
||||
|
||||
# stable
|
||||
edition = "2021"
|
||||
newline_style = "Unix"
|
||||
match_block_trailing_comma = true
|
||||
|
||||
# nightly
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Crate"
|
||||
format_code_in_doc_comments = true
|
|
@ -1,28 +0,0 @@
|
|||
use crate::api::configuration::{api_url, ImgurHandle};
|
||||
|
||||
pub async fn delete_image(c: ImgurHandle, delete_hash: String) -> Result<String, String> {
|
||||
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
||||
|
||||
let res = c
|
||||
.client
|
||||
.delete(api_url!(format!("image/{delete_hash}")))
|
||||
.header("Authorization", format!("Client-ID {}", c.client_id))
|
||||
.header(
|
||||
"User-Agent",
|
||||
format!("Imgur/{:?}", VERSION.unwrap_or("unknown")),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
let status = res.status();
|
||||
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
let body = res.text().await.map_err(|err| err.to_string())?;
|
||||
Err(format!(
|
||||
"server returned non-successful status code = {status}. body = {body}"
|
||||
))
|
||||
} else {
|
||||
Ok("If the delete hash was correct the image was deleted!".to_string())
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
use crate::api::configuration::{api_url, ImgurHandle};
|
||||
use crate::api::ImageInfo;
|
||||
|
||||
pub async fn get_image(c: ImgurHandle, image: &str) -> Result<ImageInfo, String> {
|
||||
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
||||
|
||||
let res = c
|
||||
.client
|
||||
.get(api_url!(format!("image/{image}")))
|
||||
.header("Authorization", format!("Client-ID {}", c.client_id))
|
||||
.header(
|
||||
"User-Agent",
|
||||
format!("Imgurs/{:?}", VERSION.unwrap_or("unknown")),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
let status = res.status();
|
||||
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
Err(format!(
|
||||
"server returned non-successful status code = {status}."
|
||||
))
|
||||
} else {
|
||||
let content: ImageInfo = res.json().await.map_err(|err| err.to_string())?;
|
||||
Ok(content)
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ImageInfo {
|
||||
pub data: ImageInfoData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ImageInfoData {
|
||||
pub id: String,
|
||||
pub title: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub datetime: i32,
|
||||
#[serde(rename = "type")]
|
||||
pub img_type: String,
|
||||
pub animated: bool,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub size: i32,
|
||||
pub views: i32,
|
||||
pub bandwidth: i64,
|
||||
pub favorite: bool,
|
||||
pub deletehash: Option<String>,
|
||||
pub link: String,
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
mod image_type;
|
||||
|
||||
pub mod configuration;
|
||||
pub mod delete_image;
|
||||
pub mod get_image;
|
||||
pub mod rate_limit;
|
||||
pub mod upload_image;
|
||||
|
||||
pub use configuration::ImgurHandle;
|
||||
pub use image_type::*;
|
|
@ -1,52 +0,0 @@
|
|||
use crate::api::configuration::{api_url, ImgurHandle};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RateLimitInfo {
|
||||
pub data: RateLimitData,
|
||||
pub success: bool,
|
||||
pub status: i8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RateLimitData {
|
||||
#[serde(rename = "UserLimit")]
|
||||
pub user_limit: i32,
|
||||
#[serde(rename = "UserRemaining")]
|
||||
pub user_remaining: i32,
|
||||
#[serde(rename = "UserReset")]
|
||||
pub user_reset: i32,
|
||||
#[serde(rename = "ClientLimit")]
|
||||
pub client_limit: i32,
|
||||
#[serde(rename = "ClientRemaining")]
|
||||
pub client_remaining: i32,
|
||||
}
|
||||
|
||||
pub async fn rate_limit(c: ImgurHandle) -> Result<RateLimitInfo, String> {
|
||||
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
||||
|
||||
let res = c
|
||||
.client
|
||||
.get(api_url!("credits"))
|
||||
.header("Authorization", format!("Client-ID {}", c.client_id))
|
||||
.header(
|
||||
"User-Agent",
|
||||
format!("Imgur/{:?}", VERSION.unwrap_or("unknown")),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
let status = res.status();
|
||||
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
let body = res.text().await.map_err(|err| err.to_string())?;
|
||||
Err(format!(
|
||||
"server returned non-successful status code = {status}. body = {body}"
|
||||
))
|
||||
} else {
|
||||
let content: RateLimitInfo = res.json().await.map_err(|err| err.to_string())?;
|
||||
Ok(content)
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
use crate::api::configuration::{api_url, ImgurHandle};
|
||||
use crate::api::ImageInfo;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub async fn upload_image(c: ImgurHandle, image: &str) -> Result<ImageInfo, String> {
|
||||
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
||||
|
||||
let mut form = HashMap::new();
|
||||
|
||||
form.insert("image", image.to_string());
|
||||
|
||||
let res = c
|
||||
.client
|
||||
.post(api_url!("image"))
|
||||
.header("Authorization", format!("Client-ID {}", c.client_id))
|
||||
.header(
|
||||
"User-Agent",
|
||||
format!("Imgurs/{:?}", VERSION.unwrap_or("unknown")),
|
||||
)
|
||||
.form(&form)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
let status = res.status();
|
||||
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
let body = res.text().await.map_err(|err| err.to_string())?;
|
||||
Err(format!(
|
||||
"server returned non-successful status code = {status}. body = {body}"
|
||||
))
|
||||
} else {
|
||||
let content: ImageInfo = res.json().await.map_err(|err| err.to_string())?;
|
||||
Ok(content)
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
use imgurs::api::rate_limit::*;
|
||||
use imgurs::api::ImgurHandle;
|
||||
|
||||
use log::{error, info};
|
||||
|
||||
use chrono::prelude::DateTime;
|
||||
use chrono::Utc;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
|
||||
pub async fn credits(client: ImgurHandle) {
|
||||
match rate_limit(client).await {
|
||||
Ok(i) => {
|
||||
let d = UNIX_EPOCH + Duration::from_secs(i.data.user_reset.try_into().unwrap());
|
||||
let datetime = DateTime::<Utc>::from(d);
|
||||
let timestamp_str = datetime.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
info!("User Limit {}", i.data.user_limit);
|
||||
info!("User Remaining {}", i.data.user_remaining);
|
||||
info!("User Reset {} (UTC)", timestamp_str);
|
||||
info!("Client Limit {}", i.data.client_limit);
|
||||
info!("Client Remaining {}", i.data.client_remaining);
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
use imgurs::api;
|
||||
use imgurs::api::configuration::ImgurHandle;
|
||||
|
||||
use log::{error, info};
|
||||
|
||||
pub async fn delete_image(client: ImgurHandle, delete_hash: String) {
|
||||
match api::delete_image::delete_image(client, delete_hash).await {
|
||||
Ok(i) => {
|
||||
info!("{i}")
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
use imgurs::api::{configuration::ImgurHandle, get_image::get_image};
|
||||
|
||||
use super::print_image_info;
|
||||
|
||||
use base64;
|
||||
use log::error;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
pub async fn image_info(client: ImgurHandle, path: &str) {
|
||||
let mut image: String = path.to_string();
|
||||
|
||||
if Path::new(path).exists() {
|
||||
let bytes = fs::read(path).map_err(|err| err.to_string()).unwrap();
|
||||
|
||||
image = base64::encode(bytes);
|
||||
}
|
||||
|
||||
match get_image(client, &image).await {
|
||||
Ok(i) => print_image_info(i),
|
||||
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
pub mod credits;
|
||||
pub mod delete_image;
|
||||
pub mod info_image;
|
||||
pub mod parse;
|
||||
pub mod upload_image;
|
||||
|
||||
use chrono::prelude::DateTime;
|
||||
use chrono::Utc;
|
||||
use log::info;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
|
||||
use imgurs::api::ImageInfo;
|
||||
|
||||
pub fn print_image_info(i: ImageInfo) {
|
||||
let d = UNIX_EPOCH + Duration::from_secs(i.data.datetime.try_into().unwrap());
|
||||
let datetime = DateTime::<Utc>::from(d);
|
||||
let timestamp_str = datetime.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
if i.data.title != None {
|
||||
info!(
|
||||
"Title {}",
|
||||
i.data.title.unwrap_or_else(|| "unknown".to_string())
|
||||
);
|
||||
}
|
||||
if i.data.description != None {
|
||||
info!(
|
||||
"Description {}",
|
||||
i.data.description.unwrap_or_else(|| "unknown".to_string())
|
||||
);
|
||||
}
|
||||
if i.data.deletehash != None {
|
||||
info!(
|
||||
"Deletehash {}",
|
||||
i.data.deletehash.unwrap_or_else(|| "unknown".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
info!("ID {}", i.data.id);
|
||||
info!("Upload Date {} (UTC)", timestamp_str);
|
||||
info!("Type {}", i.data.img_type);
|
||||
info!("Width {}", i.data.width);
|
||||
info!("Height {}", i.data.height);
|
||||
info!("File Size {} KB", i.data.size / 1000);
|
||||
info!("Views {}", i.data.views);
|
||||
info!("Bandwidth {}", i.data.bandwidth);
|
||||
info!("Link {}", i.data.link);
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
use clap::{AppSettings, Parser, Subcommand};
|
||||
|
||||
use imgurs::api::configuration::ImgurHandle;
|
||||
|
||||
use crate::cli::{credits::*, delete_image::*, info_image::*, upload_image::*};
|
||||
|
||||
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
||||
const NAME: Option<&str> = option_env!("CARGO_PKG_NAME");
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
name = NAME.unwrap_or("unknown"),
|
||||
about = "Imgur API CLI", long_about = None,
|
||||
version = VERSION.unwrap_or("unknown")
|
||||
)]
|
||||
struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
#[clap(about = "Get API ratelimit")]
|
||||
Credits,
|
||||
|
||||
#[clap(
|
||||
setting(AppSettings::ArgRequiredElseHelp),
|
||||
about = "Upload image to imgur"
|
||||
)]
|
||||
Upload { path: String },
|
||||
|
||||
#[clap(
|
||||
setting(AppSettings::ArgRequiredElseHelp),
|
||||
about = "Delete image from imgur"
|
||||
)]
|
||||
Delete { delete_hash: String },
|
||||
|
||||
#[clap(setting(AppSettings::ArgRequiredElseHelp), about = "Print image info")]
|
||||
Info { id: String },
|
||||
}
|
||||
|
||||
pub async fn parse(client: ImgurHandle) {
|
||||
let args = Cli::parse();
|
||||
|
||||
match &args.command {
|
||||
Commands::Credits => {
|
||||
credits(client).await;
|
||||
}
|
||||
|
||||
Commands::Upload { path } => {
|
||||
upload_image(client, path).await;
|
||||
}
|
||||
|
||||
Commands::Delete { delete_hash } => {
|
||||
delete_image(client, delete_hash.to_string()).await;
|
||||
}
|
||||
|
||||
Commands::Info { id } => {
|
||||
image_info(client, id).await;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
use super::print_image_info;
|
||||
use imgurs::api::configuration::ImgurHandle;
|
||||
|
||||
use base64;
|
||||
use log::error;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
pub async fn upload_image(client: ImgurHandle, path: &str) {
|
||||
let mut image: String = path.to_string();
|
||||
|
||||
if Path::new(path).exists() {
|
||||
let bytes = fs::read(path).map_err(|err| err.to_string()).unwrap();
|
||||
|
||||
image = base64::encode(bytes);
|
||||
}
|
||||
|
||||
match imgurs::api::upload_image::upload_image(client, &image).await {
|
||||
Ok(i) => print_image_info(i),
|
||||
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
pub mod toml;
|
|
@ -1,25 +0,0 @@
|
|||
use dirs::config_dir;
|
||||
use serde_derive::Deserialize;
|
||||
use std::fs::read_to_string;
|
||||
use toml::from_str;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub imgur: ConfigImgur,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ConfigImgur {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
pub fn parse() -> Result<Config, String> {
|
||||
let config_dir = config_dir().unwrap();
|
||||
let file_dir: String = String::from(config_dir.to_string_lossy()) + "/imgur/config.toml";
|
||||
|
||||
let toml_str = read_to_string(file_dir).map_err(|err| err.to_string())?;
|
||||
|
||||
let decode = from_str(&toml_str).map_err(|err| err.to_string())?;
|
||||
|
||||
Ok(decode)
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
use thiserror::Error;
|
||||
|
||||
/// Client Errors
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Imgur API returned non-successful status code
|
||||
#[error("server reponse non-successful status code - {0}, body = `{1}`")]
|
||||
ApiError(u16, String),
|
||||
/// Imgur API returned non-successful status code (body is too long)
|
||||
#[error("server reponse non-successful status code - {0}, (response body is too long)")]
|
||||
ApiErrorBodyTooLong(u16),
|
||||
/// Invalid file path or URL adress
|
||||
#[error("{0} is not url or file path")]
|
||||
InvalidUrlOrFile(String),
|
||||
/// Imgur API error or reqwest::Error
|
||||
#[error("send request to imgur api: {0}")]
|
||||
SendApiRequest(reqwest::Error),
|
||||
/// std::io::Error
|
||||
#[error("io error - {0}")]
|
||||
IoError(std::io::Error),
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for Error {
|
||||
fn from(err: reqwest::Error) -> Self {
|
||||
Error::SendApiRequest(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Error::IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Result` alias where the `Err` case is `imgurs::Error`
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
@ -0,0 +1,69 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ImageInfoData;
|
||||
|
||||
/// Album Info Response
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
pub struct AlbumInfo {
|
||||
/// Image Data
|
||||
pub data: AlbumInfoData,
|
||||
/// Request processed success or not.
|
||||
pub success: bool,
|
||||
/// HTTP status code from API request.
|
||||
pub status: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
pub struct AlbumInfoData {
|
||||
/// Album ID
|
||||
pub id: String,
|
||||
/// Title of the album
|
||||
pub title: Option<String>,
|
||||
/// Description of the album
|
||||
pub description: Option<String>,
|
||||
pub datetime: i64,
|
||||
pub cover: String,
|
||||
pub cover_edited: Option<String>,
|
||||
pub cover_width: i64,
|
||||
pub cover_height: i64,
|
||||
pub account_url: Option<String>,
|
||||
pub account_id: Option<AccountId>,
|
||||
pub privacy: String,
|
||||
pub layout: String,
|
||||
pub views: i64,
|
||||
/// Album link
|
||||
pub link: String,
|
||||
pub favorite: bool,
|
||||
pub nsfw: bool,
|
||||
pub section: Option<String>,
|
||||
pub images_count: i64,
|
||||
pub in_gallery: bool,
|
||||
pub is_ad: bool,
|
||||
pub include_album_ads: bool,
|
||||
pub is_album: bool,
|
||||
pub images: Vec<ImageInfoData>,
|
||||
pub ad_config: AdConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum AccountId {
|
||||
String(String),
|
||||
Int(i64),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
pub struct AdConfig {
|
||||
#[serde(rename = "safeFlags")]
|
||||
pub safe_flags: Vec<String>,
|
||||
#[serde(rename = "highRiskFlags")]
|
||||
pub high_risk_flags: Vec<String>,
|
||||
#[serde(rename = "unsafeFlags")]
|
||||
pub unsafe_flags: Vec<String>,
|
||||
#[serde(rename = "wallUnsafeFlags")]
|
||||
pub wall_unsafe_flags: Vec<String>,
|
||||
#[serde(rename = "showsAds")]
|
||||
pub shows_ads: bool,
|
||||
#[serde(rename = "showAdLevel")]
|
||||
pub show_ad_level: i64,
|
||||
}
|
|
@ -1,28 +1,25 @@
|
|||
use reqwest::Client;
|
||||
use std::fmt;
|
||||
|
||||
macro_rules! api_url (
|
||||
($path: expr) => (
|
||||
format!("{}{}", "https://api.imgur.com/3/", $path)
|
||||
);
|
||||
);
|
||||
|
||||
pub(crate) use api_url;
|
||||
use std::fmt;
|
||||
|
||||
pub struct ImgurHandle {
|
||||
pub(crate) use api_url;
|
||||
use reqwest::Client;
|
||||
|
||||
/// Imgur Client
|
||||
#[derive(Clone)]
|
||||
pub struct ImgurClient {
|
||||
/// Imgur API Client ID
|
||||
pub client_id: String,
|
||||
/// HTTP Client
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ImgurHandle {
|
||||
impl fmt::Debug for ImgurClient {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "ImgurClient - client_id: {}", self.client_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl ImgurHandle {
|
||||
pub fn new(client_id: String) -> Self {
|
||||
let client = Client::new();
|
||||
ImgurHandle { client_id, client }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Image Info Response
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
pub struct ImageInfo {
|
||||
/// Image Data
|
||||
pub data: ImageInfoData,
|
||||
/// Request processed success or not.
|
||||
pub success: bool,
|
||||
/// HTTP status code from API request.
|
||||
pub status: i32,
|
||||
}
|
||||
|
||||
/// Image Info Reponse (`data` json)
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
pub struct ImageInfoData {
|
||||
/// Image ID
|
||||
/// e.g. `iDYNKJq`
|
||||
pub id: String,
|
||||
/// Image title
|
||||
pub title: Option<String>,
|
||||
/// Description of this image
|
||||
pub description: Option<String>,
|
||||
/// Image uploaded time
|
||||
pub datetime: i32,
|
||||
/// Image type
|
||||
/// e.g. `image/png`
|
||||
#[serde(rename = "type")]
|
||||
pub img_type: String,
|
||||
/// If image if animated (gif, etc)
|
||||
pub animated: bool,
|
||||
/// Width of this image
|
||||
pub width: i32,
|
||||
/// Height of this image
|
||||
pub height: i32,
|
||||
/// Image size in bytes
|
||||
pub size: i32,
|
||||
/// Unique image views
|
||||
pub views: i32,
|
||||
/// Bandwidth used by this image
|
||||
pub bandwidth: i64,
|
||||
/// If image is added to favorite
|
||||
pub favorite: bool,
|
||||
/// Delete hash (only show after image upload)
|
||||
pub deletehash: Option<String>,
|
||||
/// Link of this image
|
||||
pub link: String,
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
mod album_type;
|
||||
mod client;
|
||||
mod image_type;
|
||||
mod requests;
|
||||
mod send_api_request;
|
||||
|
||||
pub use album_type::*;
|
||||
pub(crate) use client::api_url;
|
||||
pub use client::ImgurClient;
|
||||
pub use image_type::*;
|
||||
pub use send_api_request::*;
|
||||
use url::Url;
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
impl ImgurClient {
|
||||
/// Create a new Imgur Client
|
||||
/// ```
|
||||
/// use imgurs::ImgurClient;
|
||||
///
|
||||
/// let client = ImgurClient::new("3e3ce0d7ac14d56");
|
||||
/// ```
|
||||
pub fn new(client_id: &str) -> Self {
|
||||
let client_id = client_id.to_string();
|
||||
let client = reqwest::Client::new();
|
||||
ImgurClient { client_id, client }
|
||||
}
|
||||
|
||||
/// Create a new Imgur Client with the provided `reqwest::Client`
|
||||
///
|
||||
/// This allows for customization of the http client settings like timeout or the user agent.
|
||||
/// ```
|
||||
/// use imgurs::ImgurClient;
|
||||
/// use reqwest::Client;
|
||||
///
|
||||
/// let http_client = Client::builder().build().unwrap();
|
||||
///
|
||||
/// let client = ImgurClient::with_http_client("3e3ce0d7ac14d56", http_client);
|
||||
/// ```
|
||||
pub fn with_http_client(client_id: &str, http_client: reqwest::Client) -> Self {
|
||||
let client_id = client_id.to_string();
|
||||
ImgurClient {
|
||||
client_id,
|
||||
client: http_client,
|
||||
}
|
||||
}
|
||||
|
||||
/// Upload image to Imgur
|
||||
/// ```
|
||||
/// use imgurs::ImgurClient;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let client = ImgurClient::new("3e3ce0d7ac14d56");
|
||||
///
|
||||
/// client
|
||||
/// .upload_image("https://i.imgur.com/lFaGr1x.png")
|
||||
/// .await
|
||||
/// .expect("upload image");
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn upload_image(&self, path: &str) -> Result<ImageInfo> {
|
||||
use base64::prelude::{Engine, BASE64_STANDARD};
|
||||
|
||||
let mut image = path.to_string();
|
||||
|
||||
// check if the specified file exists if not then check if it is a url
|
||||
if std::path::Path::new(path).exists() {
|
||||
let bytes = std::fs::read(path)?;
|
||||
image = BASE64_STANDARD.encode(bytes)
|
||||
}
|
||||
// validate url adress
|
||||
else if Url::parse(path).is_err() {
|
||||
Err(Error::InvalidUrlOrFile(path.to_string()))?;
|
||||
}
|
||||
|
||||
requests::upload_image(self, image).await
|
||||
}
|
||||
|
||||
/// Delete image from Imgur
|
||||
/// ```
|
||||
/// use imgurs::ImgurClient;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let client = ImgurClient::new("3e3ce0d7ac14d56");
|
||||
///
|
||||
/// let image = client
|
||||
/// .upload_image("https://i.imgur.com/lFaGr1x.png")
|
||||
/// .await
|
||||
/// .expect("upload image");
|
||||
/// let deletehash = image.data.deletehash.unwrap();
|
||||
///
|
||||
/// client
|
||||
/// .delete_image(&deletehash)
|
||||
/// .await
|
||||
/// .expect("delete image");
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn delete_image(&self, delete_hash: &str) -> Result<()> {
|
||||
requests::delete_image(self, delete_hash).await
|
||||
}
|
||||
|
||||
/// Get Rame Limit of this Imgur Client
|
||||
/// ```
|
||||
/// use imgurs::ImgurClient;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let client = ImgurClient::new("3e3ce0d7ac14d56");
|
||||
///
|
||||
/// client.rate_limit().await.expect("get rate limit");
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn rate_limit(&self) -> Result<requests::RateLimitInfo> {
|
||||
requests::rate_limit(self).await
|
||||
}
|
||||
|
||||
/// Get image info from a Imgur
|
||||
/// ```
|
||||
/// use imgurs::ImgurClient;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let client = ImgurClient::new("3e3ce0d7ac14d56");
|
||||
///
|
||||
/// client.image_info("lFaGr1x").await.expect("delete image");
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn image_info(&self, id: &str) -> Result<ImageInfo> {
|
||||
requests::get_image(self, id).await
|
||||
}
|
||||
|
||||
/// Get album info from a Imgur
|
||||
/// ```no_run
|
||||
/// use imgurs::ImgurClient;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let client = ImgurClient::new("3e3ce0d7ac14d56");
|
||||
///
|
||||
/// client.album_info("id").await.expect("get album info");
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn album_info(&self, id: &str) -> Result<AlbumInfo> {
|
||||
requests::get_album(self, id).await
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
use reqwest::Method;
|
||||
|
||||
use crate::{api_url, send_api_request, Error, ImgurClient, Result};
|
||||
|
||||
pub async fn delete_image(client: &ImgurClient, delete_hash: &str) -> Result<()> {
|
||||
// get imgur api url
|
||||
let uri = api_url!(format!("image/{delete_hash}"));
|
||||
|
||||
// send request to imgur api
|
||||
let res = send_api_request(client, Method::DELETE, uri, None).await?;
|
||||
|
||||
// get response http code
|
||||
let status = res.status();
|
||||
|
||||
// check if an error has occurred
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
let body = res.text().await?;
|
||||
|
||||
Err(Error::ApiError(status.as_u16(), body))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
use reqwest::Method;
|
||||
|
||||
use crate::{api_url, send_api_request, AlbumInfo, Error, ImgurClient, Result};
|
||||
|
||||
pub async fn get_album(client: &ImgurClient, album: &str) -> Result<AlbumInfo> {
|
||||
// get imgur api url
|
||||
let uri = api_url!(format!("album/{album}"));
|
||||
|
||||
// send request to imgur api
|
||||
let res = send_api_request(client, Method::GET, uri, None).await?;
|
||||
|
||||
// get response http code
|
||||
let status = res.status();
|
||||
|
||||
// check if an error has occurred
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
let body = res.text().await?;
|
||||
|
||||
return Err(Error::ApiError(status.as_u16(), body));
|
||||
}
|
||||
|
||||
// return `ImageInfo`
|
||||
Ok(res.json().await?)
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
use reqwest::Method;
|
||||
|
||||
use crate::{api_url, send_api_request, Error, ImageInfo, ImgurClient, Result};
|
||||
|
||||
pub async fn get_image(client: &ImgurClient, image: &str) -> Result<ImageInfo> {
|
||||
// get imgur api url
|
||||
let uri = api_url!(format!("image/{image}"));
|
||||
|
||||
// send request to imgur api
|
||||
let res = send_api_request(client, Method::GET, uri, None).await?;
|
||||
|
||||
// get response http code
|
||||
let status = res.status();
|
||||
|
||||
// check if an error has occurred
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
let body = res.text().await?;
|
||||
|
||||
return Err(Error::ApiError(status.as_u16(), body));
|
||||
}
|
||||
|
||||
// return `ImageInfo`
|
||||
Ok(res.json().await?)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
mod delete_image;
|
||||
mod get_album;
|
||||
mod get_image;
|
||||
mod rate_limit;
|
||||
mod upload_image;
|
||||
|
||||
pub use delete_image::*;
|
||||
pub use get_album::*;
|
||||
pub use get_image::*;
|
||||
pub use rate_limit::*;
|
||||
pub use upload_image::*;
|
|
@ -0,0 +1,46 @@
|
|||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{api_url, send_api_request, Error, ImgurClient, Result};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RateLimitInfo {
|
||||
pub data: RateLimitData,
|
||||
pub success: bool,
|
||||
pub status: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RateLimitData {
|
||||
#[serde(rename = "UserLimit")]
|
||||
pub user_limit: i32,
|
||||
#[serde(rename = "UserRemaining")]
|
||||
pub user_remaining: i32,
|
||||
#[serde(rename = "UserReset")]
|
||||
pub user_reset: i32,
|
||||
#[serde(rename = "ClientLimit")]
|
||||
pub client_limit: i32,
|
||||
#[serde(rename = "ClientRemaining")]
|
||||
pub client_remaining: i32,
|
||||
}
|
||||
|
||||
pub async fn rate_limit(client: &ImgurClient) -> Result<RateLimitInfo> {
|
||||
// get imgur api url
|
||||
let uri = api_url!("credits");
|
||||
|
||||
// send request to imgur api
|
||||
let res = send_api_request(client, Method::GET, uri, None).await?;
|
||||
|
||||
// get response http code
|
||||
let status = res.status();
|
||||
|
||||
// check if an error has occurred
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
let body = res.text().await?;
|
||||
|
||||
return Err(Error::ApiError(status.as_u16(), body));
|
||||
}
|
||||
|
||||
// return `RateLimitInfo`
|
||||
Ok(res.json().await?)
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::{api_url, send_api_request, Error, ImageInfo, ImgurClient, Result};
|
||||
|
||||
pub async fn upload_image(client: &ImgurClient, image: String) -> Result<ImageInfo> {
|
||||
// create http form (hashmap)
|
||||
let mut form = HashMap::new();
|
||||
// insert image to form
|
||||
form.insert("image", image);
|
||||
|
||||
// get imgur api url
|
||||
let uri = api_url!("image");
|
||||
|
||||
// send request to imgur api
|
||||
let res = send_api_request(client, Method::POST, uri, Some(form)).await?;
|
||||
|
||||
// get response http code
|
||||
let status = res.status();
|
||||
|
||||
// check if an error has occurred
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
let body = res.text().await?;
|
||||
|
||||
// if body is too long do not return it (imgur sometimes returns whole Request)
|
||||
if body.chars().count() > 50 {
|
||||
Err(Error::ApiErrorBodyTooLong(status.as_u16()))?;
|
||||
}
|
||||
|
||||
return Err(Error::ApiError(status.as_u16(), body));
|
||||
}
|
||||
|
||||
// return `ImageInfo`
|
||||
Ok(res.json().await?)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use reqwest::{Method, Response};
|
||||
|
||||
use crate::{ImgurClient, Result};
|
||||
|
||||
/// Send request to a Imgur API
|
||||
pub async fn send_api_request(
|
||||
config: &ImgurClient,
|
||||
method: Method,
|
||||
uri: String,
|
||||
form: Option<HashMap<&str, String>>,
|
||||
) -> Result<Response> {
|
||||
// get http client
|
||||
let client = &config.client;
|
||||
|
||||
// create Request buidler
|
||||
let mut req = client.request(method, uri.as_str());
|
||||
|
||||
// add `Authorization` and `User-Agent` to Request
|
||||
req = req
|
||||
.header("Authorization", format!("Client-ID {}", config.client_id))
|
||||
.header(
|
||||
"User-Agent",
|
||||
format!("Imgur/{:?}", env!("CARGO_PKG_VERSION")),
|
||||
);
|
||||
|
||||
// if exists add HashMap to Request
|
||||
if form.is_some() {
|
||||
req = req.form(&form.unwrap())
|
||||
}
|
||||
|
||||
// build Request
|
||||
let req = req.build()?;
|
||||
|
||||
// send Request
|
||||
Ok(client.execute(req).await?)
|
||||
}
|
103
src/lib.rs
103
src/lib.rs
|
@ -1 +1,102 @@
|
|||
pub mod api;
|
||||
//! [![github]](https://github.com/M3DZIK/imgurs)
|
||||
//! [![crates-io]](https://crates.io/crates/imgurs)
|
||||
//! [![docs-rs]](https://docs.rs/imgurs)
|
||||
//!
|
||||
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
|
||||
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
|
||||
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
|
||||
//!
|
||||
//! This crate is an unofficial implementation of the [Imgur API](https://imgur.com) in Rust.
|
||||
//!
|
||||
//! # Installation
|
||||
//!
|
||||
//! ## Requirements
|
||||
//! - Rust 1.58 (earlier versions are not tested (only the latest stable version is tested!))
|
||||
//! - Network connection
|
||||
//!
|
||||
//! ## Importing
|
||||
//! The driver is available on [crates.io](https://crates.io/crates/imgurs). To use the driver in
|
||||
//! your application, simply add it to your project's `Cargo.toml`.
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! imgurs = "0.11.3"
|
||||
//! ```
|
||||
//!
|
||||
//! # Example Usage
|
||||
//!
|
||||
//! ## Create new ImgurClient
|
||||
//! ```
|
||||
//! use imgurs::ImgurClient;
|
||||
//!
|
||||
//! let client = ImgurClient::new("client_id");
|
||||
//! ```
|
||||
//!
|
||||
//! ## Image Upload
|
||||
//! ```no_run
|
||||
//! use imgurs::ImgurClient;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let client = ImgurClient::new("client_id");
|
||||
//!
|
||||
//! // From URL
|
||||
//! let info = client
|
||||
//! .upload_image("https://i.imgur.com/lFaGr1x.png")
|
||||
//! .await
|
||||
//! .unwrap();
|
||||
//! println!("{:?}", info);
|
||||
//!
|
||||
//! // From File
|
||||
//! let info = client.upload_image("path/to/file.png").await.unwrap();
|
||||
//! println!("{:?}", info);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Delete Image
|
||||
//! ```no_run
|
||||
//! use imgurs::ImgurClient;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let client = ImgurClient::new("client_id");
|
||||
//!
|
||||
//! client.delete_image("delete_hash").await.unwrap(); // delete hash
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Get Image Info
|
||||
//! ```no_run
|
||||
//! use imgurs::ImgurClient;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let client = ImgurClient::new("client_id");
|
||||
//!
|
||||
//! let info = client.image_info("lFaGr1x").await.unwrap(); // image id
|
||||
//!
|
||||
//! println!("{:?}", info);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Get Client RateLimit
|
||||
//! ```no_run
|
||||
//! use imgurs::ImgurClient;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let client = ImgurClient::new("client_id");
|
||||
//!
|
||||
//! let info = client.rate_limit().await.unwrap();
|
||||
//!
|
||||
//! println!("{:?}", info);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
mod error;
|
||||
pub use error::*;
|
||||
|
||||
#[cfg(feature = "imgur")]
|
||||
mod imgur;
|
||||
|
||||
#[cfg(feature = "imgur")]
|
||||
pub use imgur::*;
|
||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -1,24 +0,0 @@
|
|||
mod cli;
|
||||
mod config;
|
||||
|
||||
use cli::parse::parse;
|
||||
|
||||
use log::error;
|
||||
use simple_logger::SimpleLogger;
|
||||
use std::process::exit;
|
||||
|
||||
use imgurs::api::ImgurHandle;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
|
||||
let config = config::toml::parse().unwrap_or_else(|error| {
|
||||
error!("Parse toml config: {}", error);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
let client = ImgurHandle::new((&config.imgur.id).to_string());
|
||||
|
||||
parse(client).await;
|
||||
}
|
Loading…
Reference in New Issue