deskbot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,55 @@
1
+ extern crate autopilot;
2
+ use std::collections::HashMap;
3
+
4
+ fn button(symbol: String) -> autopilot::mouse::Button {
5
+ match symbol.as_str() {
6
+ "left" => autopilot::mouse::Button::Left,
7
+ "middle" => autopilot::mouse::Button::Middle,
8
+ "right" => autopilot::mouse::Button::Right,
9
+ _ => panic!("Invalid button"),
10
+ }
11
+ }
12
+
13
+ fn scroll_direction(symbol: String) -> autopilot::mouse::ScrollDirection {
14
+ match symbol.as_str() {
15
+ "up" => autopilot::mouse::ScrollDirection::Up,
16
+ "down" => autopilot::mouse::ScrollDirection::Down,
17
+ _ => panic!("Invalid scroll direction"),
18
+ }
19
+ }
20
+
21
+ pub fn location() -> HashMap<String, f64> {
22
+ let point = autopilot::mouse::location();
23
+ return HashMap::from([("x".to_string(), point.x), ("y".to_string(), point.y)]);
24
+ }
25
+
26
+ pub fn move_to(x: f64, y: f64) -> bool {
27
+ let command = autopilot::mouse::move_to(autopilot::geometry::Point::new(x, y));
28
+ match command {
29
+ Ok(_) => true,
30
+ Err(_) => false,
31
+ }
32
+ }
33
+
34
+ pub fn smooth_move(x: f64, y: f64, duration: Option<f64>) -> bool {
35
+ let command = autopilot::mouse::smooth_move(autopilot::geometry::Point::new(x, y), duration);
36
+
37
+ match command {
38
+ Ok(_) => true,
39
+ Err(_) => false,
40
+ }
41
+ }
42
+
43
+ pub fn toggle(_button: String, down: bool) -> () {
44
+ let button = button(_button);
45
+ autopilot::mouse::toggle(button, down);
46
+ }
47
+
48
+ pub fn click(_button: String, delay_ms: Option<u64>) -> () {
49
+ let button = button(_button);
50
+ autopilot::mouse::click(button, delay_ms);
51
+ }
52
+
53
+ pub fn scroll(direction: String, clicks: u32) -> () {
54
+ autopilot::mouse::scroll(scroll_direction(direction), clicks);
55
+ }
@@ -0,0 +1,34 @@
1
+ extern crate autopilot;
2
+ use std::collections::HashMap;
3
+
4
+ pub fn get_color(x: f64, y: f64) -> Option<Vec<u8>> {
5
+ let color = autopilot::screen::get_color(autopilot::geometry::Point::new(x, y));
6
+ match color {
7
+ Ok(color) => Some(color.0.to_vec()),
8
+ Err(_) => None
9
+ }
10
+ }
11
+
12
+ pub fn size() -> HashMap<String, f64> {
13
+ let size = autopilot::screen::size();
14
+
15
+ HashMap::from([
16
+ ("width".to_string(), size.width),
17
+ ("height".to_string(), size.height)
18
+ ])
19
+ }
20
+
21
+ pub fn scale() -> f64 {
22
+ autopilot::screen::scale()
23
+ }
24
+
25
+ pub fn is_point_visible(x: f64, y: f64) -> bool {
26
+ let point = autopilot::geometry::Point::new(x, y);
27
+ autopilot::screen::is_point_visible(point)
28
+ }
29
+
30
+ pub fn is_rect_visible(x: f64, y: f64, width: f64, height: f64) -> bool {
31
+ let point = autopilot::geometry::Point::new(x, y);
32
+ let size = autopilot::geometry::Size::new(width, height);
33
+ autopilot::screen::is_rect_visible(autopilot::geometry::Rect::new(point, size))
34
+ }
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskbot
4
+ class Area < Dry::Struct
5
+ attribute :x, Types::Float
6
+ attribute :y, Types::Float
7
+ attribute :width, Types::Float
8
+ attribute :height, Types::Float
9
+
10
+ transform_keys(&:to_sym)
11
+ end
12
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskbot
4
+ class Bitmap
5
+ include Dry::Monads[:result]
6
+ include Dry::Matcher.for(
7
+ :find_color,
8
+ :find,
9
+ with: Dry::Matcher::ResultMatcher
10
+ )
11
+
12
+ Rgba = Types::Array
13
+ .of(Types::Integer.constrained(gteq: 0, lteq: 255))
14
+ .constrained(size: 4)
15
+
16
+ def initialize(provider)
17
+ @provider = provider
18
+ end
19
+
20
+ def save(image_path)
21
+ @provider.save(Types::String[image_path])
22
+ end
23
+
24
+ def bounds
25
+ Area.new(@provider.bounds)
26
+ end
27
+
28
+ def color_at(x, y) # rubocop:disable Naming/MethodParameterName
29
+ red, green, blue, alpha = @provider.color_at(x, y)
30
+ Color.new(red:, green:, blue:, alpha:)
31
+ end
32
+
33
+ def eql?(image_path, tolerance: nil)
34
+ @provider.bitmap_eq(
35
+ Types::String[image_path],
36
+ Types::Float.optional[tolerance]
37
+ )
38
+ end
39
+
40
+ def find(image_path, tolerance: nil)
41
+ result = @provider.find(
42
+ Types::String[image_path],
43
+ Types::Float.optional[tolerance]
44
+ )
45
+
46
+ if result
47
+ Success(Point.new(result))
48
+ else
49
+ Failure(:not_found)
50
+ end
51
+ end
52
+
53
+ def find_color(color, tolerance: nil)
54
+ found = @provider.find_color(
55
+ Rgba[color],
56
+ Types::Float.optional[tolerance]
57
+ )
58
+
59
+ if found
60
+ Success(Point.new(found))
61
+ else
62
+ Failure(:not_found)
63
+ end
64
+ end
65
+
66
+ def all(image_path, tolerance: nil)
67
+ @provider.all(
68
+ Types::String[image_path],
69
+ Types::Float.optional[tolerance]
70
+ )
71
+ .map { |point| Point.new(point) }
72
+ end
73
+
74
+ def all_color(color, tolerance: nil)
75
+ @provider.all_color(
76
+ Rgba[color],
77
+ Types::Float.optional[tolerance]
78
+ )
79
+ .map { |point| Point.new(point) }
80
+ end
81
+
82
+ def count(image_path, tolerance: nil)
83
+ @provider.count(
84
+ Types::String[image_path],
85
+ Types::Float.optional[tolerance]
86
+ )
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskbot
4
+ class Color < Dry::Struct
5
+ attribute :red, Types::Integer
6
+ attribute :green, Types::Integer
7
+ attribute :blue, Types::Integer
8
+ attribute :alpha, Types::Integer
9
+
10
+ transform_keys do |key|
11
+ {
12
+ "r" => :red,
13
+ "g" => :green,
14
+ "b" => :blue,
15
+ "a" => :alpha
16
+ # rubocop:enable Style/StringHashKeys
17
+ }.fetch(key, key)
18
+ end
19
+
20
+ def self.from_hex(hex)
21
+ red, green, blue, alpha = hex
22
+ .match(/^#(..)(..)(..)(..)?$/)
23
+ .captures
24
+ .map do |hex_pair|
25
+ hex_pair&.hex
26
+ end
27
+
28
+ new(
29
+ red:,
30
+ green:,
31
+ blue:,
32
+ alpha: alpha || 255
33
+ )
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskbot
4
+ class Point < Dry::Struct
5
+ attribute :x, Types::Float
6
+ attribute :y, Types::Float
7
+
8
+ transform_keys(&:to_sym)
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskbot
4
+ module Providers
5
+ module Autopilot
6
+ class Bitmap # rubocop:disable Lint/EmptyClass
7
+ # Holds rust autopilot bitmap bindings
8
+ # See ext/deskbot/src/bitmap.rs for more details
9
+ #
10
+ # def bounds
11
+ # def color_at
12
+ # def bitmap_eq
13
+ # def find
14
+ # def find_color
15
+ # def all
16
+ # def all_color
17
+ # def count
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskbot
4
+ module Providers
5
+ module Autopilot
6
+ # Holds rust autopilot bindings
7
+ # See ext/deskbot/src/lib.rs for more details
8
+ #
9
+ # def type
10
+ # def alert
11
+ # def toggle_key
12
+ # def tap_key
13
+ # def mouse_location
14
+ # def move_mouse
15
+ # def smooth_move_mouse
16
+ # def toggle_mouse
17
+ # def scroll
18
+ # def click
19
+ # def color_at
20
+ # def screen_size
21
+ # def screen_scale
22
+ # def is_point_visible
23
+ # def is_area_visible
24
+ # def capture_screen
25
+ # def capture_screen_portion
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskbot
4
+ class Screen
5
+ # This is the API for any provider
6
+
7
+ def initialize(provider)
8
+ @provider = provider
9
+ end
10
+
11
+ def alert(message)
12
+ @provider.alert(Types::String[message])
13
+ end
14
+
15
+ def type(text, flags: [], wpm: 60.0, noise: 0.0)
16
+ @provider.type(
17
+ Types::String[text],
18
+ Types::Flags[flags],
19
+ Types::Float[wpm],
20
+ Types::Float[noise]
21
+ )
22
+ end
23
+
24
+ def toggle_key(key, down: true, flags: [], modifier_delay_ms: 0.0)
25
+ @provider.toggle_key(
26
+ Types::Character[key],
27
+ Types::Bool[down],
28
+ Types::Flags[flags],
29
+ Types::Float[modifier_delay_ms]
30
+ )
31
+ end
32
+
33
+ def tap_key(key, flags: [], delay_ms: 0.0, modifier_delay_ms: 0.0)
34
+ @provider.tap_key(
35
+ Types::Character[key],
36
+ Types::Flags[flags],
37
+ Types::Float[delay_ms],
38
+ Types::Float[modifier_delay_ms]
39
+ )
40
+ end
41
+
42
+ def mouse_location
43
+ Point.new(@provider.mouse_location)
44
+ end
45
+
46
+ def move_mouse(x, y) # rubocop:disable Naming/MethodParameterName
47
+ @provider.move_mouse(
48
+ Types::Coercible::Float[x],
49
+ Types::Coercible::Float[y]
50
+ )
51
+ end
52
+
53
+ def smooth_move_mouse(x, y, duration: 1) # rubocop:disable Naming/MethodParameterName
54
+ @provider.smooth_move_mouse(
55
+ Types::Coercible::Float[x],
56
+ Types::Coercible::Float[y],
57
+ Types::Coercible::Float[duration]
58
+ )
59
+ end
60
+
61
+ def toggle_mouse(button = "left", down: true)
62
+ @provider.toggle_mouse(
63
+ Types::Button[button],
64
+ Types::Bool[down]
65
+ )
66
+ end
67
+
68
+ def click(button = "left", delay_ms: nil)
69
+ @provider.click(
70
+ Types::Button[button],
71
+ Types::Float.optional[delay_ms]
72
+ )
73
+ end
74
+
75
+ def scroll(direction = "up", clicks: 1)
76
+ @provider.scroll(
77
+ Types::ScrollDirection[direction],
78
+ Types::Integer[clicks]
79
+ )
80
+ end
81
+
82
+ def color_at(x, y) # rubocop:disable Naming/MethodParameterName
83
+ red, green, blue, alpha = @provider.color_at(
84
+ Types::Coercible::Float[x],
85
+ Types::Coercible::Float[y]
86
+ )
87
+
88
+ Color.new(red:, green:, blue:, alpha:)
89
+ end
90
+
91
+ def size
92
+ Size.new(@provider.screen_size)
93
+ end
94
+
95
+ def scale
96
+ @provider.screen_scale
97
+ end
98
+
99
+ def point_visible?(x, y) # rubocop:disable Naming/MethodParameterName
100
+ @provider.is_point_visible(
101
+ Types::Coercible::Float[x],
102
+ Types::Coercible::Float[y]
103
+ )
104
+ end
105
+
106
+ def area_visible?(x:, y:, width:, height:) # rubocop:disable Naming/MethodParameterName
107
+ @provider.is_area_visible(
108
+ Types::Coercible::Float[x],
109
+ Types::Coercible::Float[y],
110
+ Types::Coercible::Float[width],
111
+ Types::Coercible::Float[height]
112
+ )
113
+ end
114
+
115
+ def capture
116
+ bitmap = @provider.capture_screen
117
+ Deskbot::Bitmap.new(bitmap)
118
+ end
119
+
120
+ def capture_area(x:, y:, width:, height:) # rubocop:disable Naming/MethodParameterName
121
+ bitmap = @provider.capture_screen_area(
122
+ Types::Coercible::Float[x],
123
+ Types::Coercible::Float[y],
124
+ Types::Coercible::Float[width],
125
+ Types::Coercible::Float[height]
126
+ )
127
+
128
+ Deskbot::Bitmap.new(bitmap)
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskbot
4
+ class Size < Dry::Struct
5
+ attribute :width, Types::Float
6
+ attribute :height, Types::Float
7
+
8
+ transform_keys(&:to_sym)
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskbot
4
+ module Types
5
+ include Dry.Types(default: :strict)
6
+
7
+ Flag = Types::Coercible::String.enum(
8
+ "shift",
9
+ "control",
10
+ "alt",
11
+ "meta",
12
+ "help"
13
+ )
14
+
15
+ Button = Types::Coercible::String.enum(
16
+ "left",
17
+ "middle",
18
+ "right"
19
+ )
20
+
21
+ Flags = Types::Array.of(Flag)
22
+
23
+ Character = Types::Coercible::String.constrained(size: 1)
24
+
25
+ ScrollDirection = Types::Coercible::String.enum(
26
+ "up",
27
+ "down"
28
+ )
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskbot
4
+ VERSION = "0.1.0"
5
+ end
data/lib/deskbot.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types"
4
+ require "dry/struct"
5
+ require "dry/monads"
6
+ require "dry/matcher/result_matcher"
7
+
8
+ require_relative "deskbot/version"
9
+ require_relative "deskbot/deskbot"
10
+ require_relative "deskbot/types"
11
+ require_relative "deskbot/point"
12
+ require_relative "deskbot/color"
13
+ require_relative "deskbot/size"
14
+ require_relative "deskbot/area"
15
+ require_relative "deskbot/bitmap"
16
+
17
+ require "deskbot/providers/autopilot/bitmap"
18
+ require "deskbot/providers/autopilot"
19
+
20
+ require "deskbot/screen"
21
+
22
+ module Deskbot
23
+ class Error < StandardError; end
24
+
25
+ def self.screen(provider: Providers::Autopilot)
26
+ @screen ||= Screen.new(provider)
27
+ end
28
+ end
data/sig/deskbot.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Deskbot
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deskbot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nolan J Tait
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-05-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-matcher
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-monads
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-struct
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dry-types
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.7'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.7'
69
+ description: Ruby desktop automation
70
+ email:
71
+ - nolanjtait@gmail.com
72
+ executables: []
73
+ extensions:
74
+ - ext/deskbot/Cargo.toml
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".rspec"
78
+ - ".rubocop.yml"
79
+ - CHANGELOG.md
80
+ - Cargo.lock
81
+ - Cargo.toml
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - ext/deskbot/Cargo.toml
86
+ - ext/deskbot/extconf.rb
87
+ - ext/deskbot/src/alert.rs
88
+ - ext/deskbot/src/bitmap.rs
89
+ - ext/deskbot/src/keys.rs
90
+ - ext/deskbot/src/lib.rs
91
+ - ext/deskbot/src/mouse.rs
92
+ - ext/deskbot/src/screen.rs
93
+ - lib/deskbot.rb
94
+ - lib/deskbot/area.rb
95
+ - lib/deskbot/bitmap.rb
96
+ - lib/deskbot/color.rb
97
+ - lib/deskbot/point.rb
98
+ - lib/deskbot/providers/autopilot.rb
99
+ - lib/deskbot/providers/autopilot/bitmap.rb
100
+ - lib/deskbot/screen.rb
101
+ - lib/deskbot/size.rb
102
+ - lib/deskbot/types.rb
103
+ - lib/deskbot/version.rb
104
+ - sig/deskbot.rbs
105
+ homepage: https://github.com/nolantait/desktbot
106
+ licenses:
107
+ - MIT
108
+ metadata:
109
+ homepage_uri: https://github.com/nolantait/desktbot
110
+ source_code_uri: https://github.com/nolantait/desktbot
111
+ changelog_uri: https://github.com/nolantait/desktbot
112
+ rubygems_mfa_required: 'true'
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '3.3'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: 3.3.11
127
+ requirements: []
128
+ rubygems_version: 3.5.9
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: Ruby dekstop automation
132
+ test_files: []