ruby-macrodroid 0.8.11 → 0.9.3

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.
@@ -3,6 +3,10 @@
3
3
 
4
4
  # This file contains the following classes:
5
5
  #
6
+ # ## Nlp classes
7
+ #
8
+ # TriggersNlp ActionsNlp ConstraintsNlp
9
+ #
6
10
  # ## Macro class
7
11
  #
8
12
  # Macro
@@ -12,11 +16,372 @@
12
16
  VAR_TYPES = {
13
17
  String: [2, :string_value],
14
18
  TrueClass: [0, :boolean_value],
15
- TrueClass: [0, :boolean_value],
19
+ FalseClass: [0, :boolean_value],
16
20
  Integer: [1, :int_value],
17
21
  Float: [3, :decimal_value]
18
22
  }
19
23
 
24
+ class TriggersNlp
25
+ include AppRoutes
26
+
27
+ def initialize(macro=nil)
28
+
29
+ super()
30
+ params = {macro: macro}
31
+ triggers(params)
32
+
33
+ end
34
+
35
+ def triggers(params)
36
+
37
+ # e.g. at 7:30pm daily
38
+ get /^(?:at )?(\d+:\d+(?:[ap]m)?) daily/i do |time, days|
39
+ [TimerTrigger, {time: time,
40
+ days: %w(Mon Tue Wed Thu Fri Sat Sun).join(', ')}]
41
+ end
42
+
43
+ get /^(?:at )?(\d+:\d+(?:[ap]m)?) (?:on )?(.*)/i do |time, days|
44
+ [TimerTrigger, {time: time, days: days}]
45
+ end
46
+
47
+ # time.is? 'at 18:30pm on Mon or Tue'
48
+ get /^time.is\? ['"](?:at )?(\d+:\d+(?:[ap]m)?) (?:on )?(.*)['"]/i do |time, days|
49
+ [TimerTrigger, {time: time, days: days.gsub(' or ',', ')}]
50
+ end
51
+
52
+ get /^shake[ _]device\??$/i do
53
+ [ShakeDeviceTrigger, {}]
54
+ end
55
+
56
+ get /^Flip Device (.*)$/i do |motion|
57
+ facedown = motion =~ /Face Up (?:->|to) Face Down/i
58
+ [FlipDeviceTrigger, {face_down: facedown }]
59
+ end
60
+
61
+ get /^flip_device_down\?$/i do
62
+ [FlipDeviceTrigger, {face_down: true }]
63
+ end
64
+
65
+ get /^flip_device_up\?$/i do
66
+ [FlipDeviceTrigger, {face_down: false }]
67
+ end
68
+
69
+ get /^Failed Login Attempt$/i do
70
+ [FailedLoginTrigger, {}]
71
+ end
72
+
73
+ get /^failed_login?$/i do
74
+ [FailedLoginTrigger, {}]
75
+ end
76
+
77
+ get /^Geofence (Entry|Exit) \(([^\)]+)/i do |direction, name|
78
+ enter_area = direction.downcase.to_sym == :entry
79
+ [GeofenceTrigger, {name: name, enter_area: enter_area}]
80
+ end
81
+
82
+ get /^location (entered|exited) \(([^\)]+)/i do |direction, name|
83
+ enter_area = direction.downcase.to_sym == :entered
84
+ [GeofenceTrigger, {name: name, enter_area: enter_area}]
85
+ end
86
+
87
+ # eg. Proximity Sensor (Near)
88
+ #
89
+ get /^Proximity Sensor \(([^\)]+)\)/i do |distance|
90
+
91
+ [ProximityTrigger, {distance: distance}]
92
+ end
93
+
94
+ # eg. Proximity near
95
+ #
96
+ get /^Proximity (near|far|slow wave|fast wave)/i do |distance|
97
+
98
+ [ProximityTrigger, {distance: distance}]
99
+ end
100
+
101
+ get /^WebHook \(Url\)/i do
102
+ [WebHookTrigger, params]
103
+ end
104
+
105
+ get /^WebHook/i do
106
+ [WebHookTrigger, params]
107
+ end
108
+
109
+ get /^wh/i do
110
+ [WebHookTrigger, params]
111
+ end
112
+
113
+ # MacroDroid specific ---------------------------------------------------------------
114
+
115
+ get /^EmptyTrigger$/i do
116
+ [EmptyTrigger, params]
117
+ end
118
+
119
+ end
120
+
121
+ alias find_trigger run_route
122
+
123
+ def to_s(colour: false)
124
+ 'TriggersNlp ' + @h.inspect
125
+ end
126
+
127
+ alias to_summary to_s
128
+ end
129
+
130
+ class ActionsNlp
131
+ include AppRoutes
132
+
133
+ def initialize(macro=nil)
134
+
135
+ super()
136
+ params = {macro: macro}
137
+ actions(params)
138
+
139
+ end
140
+
141
+ def actions(params)
142
+
143
+ # e.g. message popup: hello world!
144
+ get /^message popup: (.*)/i do |msg|
145
+ [ToastAction, {msg: msg}]
146
+ end
147
+
148
+ # e.g. Popup Message 'hello world!'
149
+ get /^Popup[ _]Message ['"]([^'"]+)/i do |msg|
150
+ [ToastAction, {msg: msg}]
151
+ end
152
+
153
+ # e.g. Popup Message\n hello world!
154
+ get /^Popup Message\n\s+(.*)/im do |msg|
155
+ [ToastAction, {msg: msg}]
156
+ end
157
+
158
+ # e.g. Popup Message
159
+ get /^Popup Message$/i do
160
+ [ToastAction, {}]
161
+ end
162
+
163
+ # e.g. say current time
164
+ get /^say current[ _]time/i do
165
+ [SayTimeAction, {}]
166
+ end
167
+
168
+ get /^Torch :?(.*)/i do |onoffstate|
169
+ state = %w(on off toggle).index onoffstate.downcase
170
+ [CameraFlashLightAction, {state: state}]
171
+ end
172
+
173
+ get /^Take Picture/i do
174
+ [TakePictureAction, {}]
175
+ end
176
+
177
+ get /^take_picture/i do
178
+ [TakePictureAction, {}]
179
+ end
180
+
181
+ # -- DEVICE ACTIONS ------------------------------------------------------
182
+
183
+ get /^Speak text \(([^\)]+)\)/i do |text|
184
+ [SpeakTextAction, {text: text}]
185
+ end
186
+
187
+ get /^Speak text ['"]([^'"]+)/i do |text|
188
+ [SpeakTextAction, {text: text}]
189
+ end
190
+
191
+ get /^Speak text$/i do |text|
192
+ [SpeakTextAction, {}]
193
+ end
194
+
195
+ get /^Vibrate \(([^\)]+)/i do |pattern|
196
+ [VibrateAction, {pattern: pattern}]
197
+ end
198
+
199
+ # e.g. Display Notification: Hi there: This is the body of the message
200
+ get /^Display Notification: ([^:]+): [^$]+$/i do |subject, text|
201
+ [NotificationAction, {subject: subject, text: text}]
202
+ end
203
+
204
+
205
+ # e.g. Enable Wifi
206
+ get /^(Enable|Disable) Wifi$/i do |raw_state|
207
+
208
+ state = raw_state.downcase.to_sym == :enable ? 0 : 1
209
+ [SetWifiAction, {state: state}]
210
+
211
+ end
212
+
213
+ # e.g. Play: Altair
214
+ get /^Play: (.*)$/i do |name|
215
+
216
+ [PlaySoundAction, {file_path: name}]
217
+
218
+ end
219
+
220
+ # e.g. Launch Settings
221
+ get /^Launch (.*)$/i do |application|
222
+
223
+ h = {
224
+ application_name: application,
225
+ package_to_launch: 'com.android.' + application.downcase
226
+ }
227
+ [LaunchActivityAction, h]
228
+
229
+ end
230
+
231
+ # e.g. HTTP GET http://someurl.com/something
232
+ get /^HTTP GET ([^$]+)$/i do |url|
233
+
234
+ [OpenWebPageAction, url_to_open: url]
235
+
236
+ end
237
+
238
+ get /^HTTP GET$/i do
239
+
240
+ [OpenWebPageAction, {}]
241
+
242
+ end
243
+
244
+ # e.g. webhook entered_kitchen
245
+ #
246
+ get /(?:webhook|HTTP GET) ([^$]+)$/i do |s|
247
+ key = s =~ /^http/ ? :url_to_open : :identifier
248
+ [OpenWebPageAction, {key => s}]
249
+ end
250
+
251
+ #
252
+ get /^WebHook \(Url\)/i do
253
+ [OpenWebPageAction, params]
254
+ end
255
+
256
+ # e.g. webhook entered_kitchen
257
+ #
258
+ get /^webhook$/i do
259
+ [OpenWebPageAction, params]
260
+ end
261
+
262
+ # -- Location ---------------------------------------------------------
263
+
264
+ get /^Force Location Update$/i do
265
+ [ForceLocationUpdateAction, params]
266
+ end
267
+
268
+ get /^Share Location$/i do
269
+ [ShareLocationAction, params]
270
+ end
271
+
272
+ #a: Keep Device Awake Screen On Until Disabled
273
+ #
274
+ get /Keep Device Awake Screen On Until Disabled/i do
275
+ [KeepAwakeAction, {enabled: true, permanent: true, screen_option: 0}]
276
+ end
277
+
278
+
279
+ #a: Keep Device Awake Screen On 1h 1m 1s
280
+ #
281
+ get /Keep Device Awake Screen On ([^$]+)/i do |duration|
282
+
283
+ a = duration.split.map(&:to_i)
284
+ secs = Subunit.new(units={minutes:60, hours:60, seconds: 60}, a).to_i
285
+
286
+ h = {
287
+ permanent: true, screen_option: 0, seconds_to_stay_awake_for: secs
288
+ }
289
+ [KeepAwakeAction, h]
290
+ end
291
+
292
+ get /Keep Device Awake$/i do
293
+ [KeepAwakeAction, params]
294
+ end
295
+
296
+ #a: Disable Keep Awake
297
+ #
298
+ get /Disable Keep Awake/i do
299
+ [KeepAwakeAction, {enabled: false, screen_option: 0}]
300
+ end
301
+
302
+ #e.g a: if Airplane mode enabled
303
+ #
304
+ get /if (.*)/i do
305
+ [IfConditionAction, {}]
306
+ end
307
+
308
+ get /End If/i do
309
+ [EndIfAction, {}]
310
+ end
311
+
312
+ # -- MacroDroid Specific ------------------------------------------------
313
+ #
314
+ get /^Set Variable$/i do
315
+ [SetVariableAction, {}]
316
+ end
317
+
318
+ end
319
+
320
+ alias find_action run_route
321
+
322
+
323
+ end
324
+
325
+ class ConstraintsNlp
326
+ include AppRoutes
327
+
328
+ def initialize()
329
+
330
+ super()
331
+ params = {}
332
+ constraints(params)
333
+
334
+ end
335
+
336
+ def constraints(params)
337
+
338
+ # Device State
339
+
340
+ get /^Device (locked|unlocked)/i do |state|
341
+ [DeviceLockedConstraint, {locked: state.downcase == 'locked'}]
342
+ end
343
+
344
+ get /^airplane mode (.*)/i do |state|
345
+ [AirplaneModeConstraint, {enabled: (state =~ /^enabled|on$/i) == 0}]
346
+ end
347
+
348
+ #
349
+
350
+ # -- Sensors -----------------------------------
351
+ #
352
+ get /^Light Sensor (Less|Greater) than (50.0)lx/i do |operator, val|
353
+
354
+ level, option = operator.downcase == 'less' ? [-1,0] : [1,1]
355
+
356
+ h = {
357
+ light_level: level,
358
+ light_level_float: val,
359
+ option: option
360
+ }
361
+
362
+ [LightLevelConstraint, h]
363
+ end
364
+
365
+ get /^Proximity Sensor: (Near|Far)/i do |distance|
366
+ [ProximitySensorConstraint, {near: distance.downcase == 'near'}]
367
+ end
368
+
369
+
370
+ # -- Screen and Speaker ---------------------------
371
+ #
372
+ get /^Screen (On|Off)/i do |state|
373
+ [ScreenOnOffConstraint, {screen_on: state.downcase == 'on'}]
374
+ end
375
+
376
+ end
377
+
378
+ alias find_constraint run_route
379
+
380
+ end
381
+
382
+
383
+ class MacroError < Exception
384
+ end
20
385
 
21
386
  class Macro
22
387
  using ColouredText
@@ -24,15 +389,17 @@ class Macro
24
389
 
25
390
  attr_reader :local_variables, :triggers, :actions, :constraints,
26
391
  :guid, :deviceid
27
- attr_accessor :title, :description
392
+ attr_accessor :title, :description, :remote_url
28
393
 
29
- def initialize(name=nil, geofences: nil, deviceid: nil, debug: false)
394
+ def initialize(name=nil, geofences: nil, deviceid: nil, remote_url: nil,
395
+ debug: false)
30
396
 
31
397
  @title, @geofences, @deviceid, @debug = name, geofences, deviceid, debug
398
+ @remote_url = remote_url
32
399
 
33
400
  puts 'inside Macro#initialize' if @debug
34
401
 
35
- @local_variables, @triggers, @actions, @constraints = [], [], [], []
402
+ @local_variables, @triggers, @actions, @constraints = {}, [], [], []
36
403
  @h = {}
37
404
 
38
405
  end
@@ -59,9 +426,13 @@ class Macro
59
426
  end
60
427
 
61
428
  def to_h()
429
+
430
+ a = @local_variables.map do |k,v|
431
+ varify(k,v).to_camelcase.map{|key,value| ['m_' + key, value]}.to_h
432
+ end
62
433
 
63
434
  h = {
64
- local_variables: varify(@local_variables),
435
+ local_variables: a,
65
436
  m_trigger_list: @triggers.map(&:to_h),
66
437
  m_action_list: @actions.map(&:to_h),
67
438
  m_category: @category,
@@ -71,7 +442,7 @@ class Macro
71
442
  m_excludeLog: false,
72
443
  m_GUID: guid(),
73
444
  m_isOrCondition: false,
74
- m_enabled: false,
445
+ m_enabled: true,
75
446
  m_descriptionOpen: false,
76
447
  m_headingColor: 0
77
448
  }
@@ -96,7 +467,7 @@ class Macro
96
467
  if h[:local_variables].any? and h[:local_variables].first.any? then
97
468
 
98
469
  @local_variables = h[:local_variables].map do |var|
99
-
470
+
100
471
  val = case var[:type]
101
472
  when 0 # boolean
102
473
  var[:boolean_value]
@@ -115,7 +486,7 @@ class Macro
115
486
 
116
487
  # fetch the triggers
117
488
  @triggers = h[:trigger_list].map do |trigger|
118
- puts 'trigger: ' + trigger.inspect
489
+ puts 'trigger: ' + trigger.inspect if @debug
119
490
  #exit
120
491
  object(trigger.to_snake_case)
121
492
 
@@ -194,31 +565,103 @@ class Macro
194
565
 
195
566
  @title = node.text('macro') || node.attributes[:name]
196
567
 
197
- @local_variables = node.xpath('variable').map do |e|
198
- e.text.split(/: +/,2)
568
+ d = node.element('description')
569
+
570
+ if d then
571
+
572
+ desc = []
573
+ desc << d.text.strip
574
+
575
+ if d.element('item/description') then
576
+ desc << d.text('item/description').strip
577
+ end
578
+
579
+ @description = desc.join("\n")
580
+
199
581
  end
200
582
 
583
+ node.xpath('variable').each {|e| set_var(*e.text.to_s.split(/: */,2)) }
584
+
201
585
  #@description = node.attributes[:description]
202
586
 
203
587
  tp = TriggersNlp.new(self)
204
588
 
205
- @triggers = node.xpath('trigger').map do |e|
589
+ @triggers = node.xpath('trigger').flat_map do |e|
206
590
 
207
591
  r = tp.find_trigger e.text
208
592
 
209
593
  puts 'found trigger ' + r.inspect if @debug
210
594
 
211
- if r then
212
- if r[0] == GeofenceTrigger then
213
- GeofenceTrigger.new(r[1], geofences: @geofences)
595
+ item = e.element('item')
596
+ if item then
597
+
598
+ if item.element('description') then
599
+
600
+ item.xpath('description').map do |description|
601
+
602
+ inner_lines = description.text.to_s.strip.lines
603
+ puts 'inner_lines: ' + inner_lines.inspect if @debug
604
+
605
+ trigger = if e.text.to_s.strip.empty? then
606
+ inner_lines.shift.strip
607
+ else
608
+ e.text.strip
609
+ end
610
+
611
+ puts 'trigger: ' + trigger.inspect if @debug
612
+
613
+ r = tp.find_trigger trigger
614
+ puts 'r: ' + r.inspect if @debug
615
+ #o = r[0].new([description, self]) if r
616
+ o = object_create(r[0], [description, self]) if r
617
+ puts 'after o' if @debug
618
+ o
619
+
620
+ end
621
+
622
+ else
623
+
624
+ trigger = e.text.strip
625
+ r = tp.find_trigger trigger
626
+
627
+ a = e.xpath('item/*')
628
+
629
+ h = if a.any? then
630
+ a.map {|node| [node.name.to_sym, node.text.to_s]}.to_h
631
+ else
632
+ {}
633
+ end
634
+
635
+ r = tp.find_trigger trigger
636
+ #r[0].new(h) if r
637
+ if r then
638
+ object_create(r[0], h)
639
+ else
640
+ raise MacroError, 'App-routes: Trigger "' + trigger + '" not found'
641
+ end
642
+
643
+ end
644
+
645
+ else
646
+
647
+ trigger = e.text.strip
648
+ r = tp.find_trigger trigger
649
+ #r[0].new(r[1]) if r
650
+
651
+ if r then
652
+ object_create(r[0],r[1])
214
653
  else
215
- r[0].new(r[1])
654
+ raise MacroError, 'App-routes: Trigger "' + trigger + '" not found'
216
655
  end
656
+
217
657
  end
218
658
 
659
+
219
660
  end
661
+
662
+
220
663
 
221
- ap = ActionsNlp.new
664
+ ap = ActionsNlp.new self
222
665
 
223
666
  @actions = node.xpath('action').flat_map do |e|
224
667
 
@@ -245,8 +688,10 @@ class Macro
245
688
 
246
689
  r = ap.find_action action
247
690
  puts 'r: ' + r.inspect if @debug
248
- o = r[0].new(description) if r
249
- puts 'after o'
691
+ puts 'description: ' + description.xml.inspect if @debug
692
+ #o = r[0].new([description, self]) if r
693
+ o = object_create(r[0],[description, self]) if r
694
+ puts 'after o' if @debug
250
695
  o
251
696
 
252
697
  end
@@ -254,6 +699,7 @@ class Macro
254
699
  else
255
700
 
256
701
  action = e.text.strip
702
+ puts 'action: ' + action.inspect if @debug
257
703
  r = ap.find_action action
258
704
 
259
705
  a = e.xpath('item/*')
@@ -263,9 +709,11 @@ class Macro
263
709
  else
264
710
  {}
265
711
  end
712
+ puts 'h: ' + h.inspect if @debug
266
713
 
267
- r = ap.find_action action
268
- r[0].new(h) if r
714
+ #r = ap.find_action action
715
+ #r[0].new(h.merge(macro: self)) if r
716
+ object_create(r[0], h.merge(macro: self)) if r
269
717
 
270
718
  end
271
719
 
@@ -273,7 +721,8 @@ class Macro
273
721
 
274
722
  action = e.text.strip
275
723
  r = ap.find_action action
276
- r[0].new(r[1]) if r
724
+ #r[0].new(r[1]) if r
725
+ object_create(r[0],r[1]) if r
277
726
 
278
727
  end
279
728
 
@@ -286,9 +735,7 @@ class Macro
286
735
  r = cp.find_constraint e.text
287
736
  puts 'found constraint ' + r.inspect if @debug
288
737
 
289
- if r then
290
- r[0].new(r[1])
291
- end
738
+ object_create(r[0], r[1]) if r
292
739
 
293
740
  end
294
741
 
@@ -333,6 +780,27 @@ class Macro
333
780
  def set_env()
334
781
  @triggers.each(&:set_env)
335
782
  end
783
+
784
+ def set_var(label, v='')
785
+
786
+ value = if v.to_f.to_s == v
787
+ v.to_f
788
+ elsif v.downcase == 'true'
789
+ true
790
+ elsif v.downcase == 'false'
791
+ false
792
+ elsif v.to_i.to_s == v
793
+ v.to_i
794
+ else
795
+ v
796
+ end
797
+
798
+ if not @local_variables.has_key? label.to_sym then
799
+ @local_variables.merge!({label.to_sym => value})
800
+ end
801
+
802
+ varify(label, value)
803
+ end
336
804
 
337
805
  def to_pc()
338
806
 
@@ -378,13 +846,21 @@ EOF
378
846
  puts 'before triggers' if @debug
379
847
 
380
848
  a << @triggers.map do |x|
849
+
850
+ puts 'x: ' + x.inspect if @debug
851
+ raise 'Macro#to_s trigger cannot be nil' if x.nil?
852
+
381
853
  s =-x.to_s(colour: colour)
854
+ puts 's: ' + s.inspect if @debug
382
855
 
383
856
  s2 = if s.lines.length > 1 then
384
857
  "\n" + s.lines.map {|x| x.prepend (' ' * (indent+1)) }.join
385
858
  else
386
859
  ' ' + s
387
860
  end
861
+
862
+ puts 's2: ' + s2.inspect if @debug
863
+
388
864
  #s.lines > 1 ? "\n" + x : x
389
865
  (colour ? "t".bg_red.gray.bold : 't') + ":" + s2
390
866
  end.join("\n")
@@ -519,42 +995,47 @@ EOF
519
995
 
520
996
  puts ('inside object h:' + h.inspect).debug if @debug
521
997
  klass = Object.const_get h[:class_type]
522
- puts klass.inspect.highlight if $debug
998
+ puts klass.inspect.highlight if @debug
523
999
 
524
1000
  if klass == GeofenceTrigger then
525
- puts 'GeofenceTrigger found'.highlight if $debug
1001
+ puts 'GeofenceTrigger found'.highlight if @debug
526
1002
  GeofenceTrigger.new(h, geofences: @geofences)
527
1003
  else
528
- puts 'before klass'
1004
+ puts 'before klass' if @debug
529
1005
  h2 = h.merge( macro: self)
530
- puts 'h2: ' + h2.inspect
1006
+ puts 'h2: ' + h2.inspect if @debug
531
1007
  r = klass.new h2
532
- puts 'r:' + r.inspect
1008
+ puts 'r:' + r.inspect if @debug
533
1009
  r
534
1010
 
535
1011
  end
536
1012
 
537
1013
  end
538
1014
 
539
- def varify(local_variables)
540
-
541
-
542
- local_variables.map do |key, value|
543
-
544
-
545
- type = VAR_TYPES[value.class.to_s.to_sym]
546
-
547
- h = {
548
- boolean_value: false,
549
- decimal_value: 0.0,
550
- int_value: 0,
551
- name: key,
552
- string_value: '',
553
- type: type[0]
554
- }
555
- h[type[1]] = value
556
- h
1015
+ def object_create(klass, *args)
1016
+
1017
+ begin
1018
+ klass.new(*args)
1019
+ rescue
1020
+ raise MacroError, klass.to_s + ': ' + ($!).to_s
557
1021
  end
1022
+ end
1023
+
1024
+ def varify(label, value='')
1025
+
1026
+
1027
+ type = VAR_TYPES[value.class.to_s.to_sym]
1028
+
1029
+ h = {
1030
+ boolean_value: false,
1031
+ decimal_value: 0.0,
1032
+ int_value: 0,
1033
+ name: label,
1034
+ string_value: '',
1035
+ type: type[0]
1036
+ }
1037
+ h[type[1]] = value
1038
+ h
558
1039
 
559
1040
  end
560
1041