ruby-macrodroid 0.8.10 → 0.9.2

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,25 +3,361 @@
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
9
13
 
14
+
15
+
16
+ VAR_TYPES = {
17
+ String: [2, :string_value],
18
+ TrueClass: [0, :boolean_value],
19
+ FalseClass: [0, :boolean_value],
20
+ Integer: [1, :int_value],
21
+ Float: [3, :decimal_value]
22
+ }
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
+ # e.g. Display Notification: Hi there: This is the body of the message
196
+ get /^Display Notification: ([^:]+): [^$]+$/i do |subject, text|
197
+ [NotificationAction, {subject: subject, text: text}]
198
+ end
199
+
200
+
201
+ # e.g. Enable Wifi
202
+ get /^(Enable|Disable) Wifi$/i do |raw_state|
203
+
204
+ state = raw_state.downcase.to_sym == :enable ? 0 : 1
205
+ [SetWifiAction, {state: state}]
206
+
207
+ end
208
+
209
+ # e.g. Play: Altair
210
+ get /^Play: (.*)$/i do |name|
211
+
212
+ [PlaySoundAction, {file_path: name}]
213
+
214
+ end
215
+
216
+ # e.g. Launch Settings
217
+ get /^Launch (.*)$/i do |application|
218
+
219
+ h = {
220
+ application_name: application,
221
+ package_to_launch: 'com.android.' + application.downcase
222
+ }
223
+ [LaunchActivityAction, h]
224
+
225
+ end
226
+
227
+ # e.g. HTTP GET http://someurl.com/something
228
+ get /^HTTP GET ([^$]+)$/i do |url|
229
+
230
+ [OpenWebPageAction, url_to_open: url]
231
+
232
+ end
233
+
234
+ get /^HTTP GET$/i do
235
+
236
+ [OpenWebPageAction, {}]
237
+
238
+ end
239
+
240
+ # e.g. webhook entered_kitchen
241
+ #
242
+ get /(?:webhook|HTTP GET) ([^$]+)$/i do |s|
243
+ key = s =~ /^http/ ? :url_to_open : :identifier
244
+ [OpenWebPageAction, {key => s}]
245
+ end
246
+
247
+ #
248
+ get /^WebHook \(Url\)/i do
249
+ [OpenWebPageAction, params]
250
+ end
251
+
252
+ # e.g. webhook entered_kitchen
253
+ #
254
+ get /^webhook$/i do
255
+ [OpenWebPageAction, params]
256
+ end
257
+
258
+ # -- Location ---------------------------------------------------------
259
+
260
+ get /^Force Location Update$/i do
261
+ [ForceLocationUpdateAction, params]
262
+ end
263
+
264
+ get /^Share Location$/i do
265
+ [ShareLocationAction, params]
266
+ end
267
+
268
+ #a: Keep Device Awake Screen On Until Disabled
269
+ #
270
+ get /Keep Device Awake Screen On Until Disabled/i do
271
+ [KeepAwakeAction, {enabled: true, permanent: true, screen_option: 0}]
272
+ end
273
+
274
+
275
+ #a: Keep Device Awake Screen On 1h 1m 1s
276
+ #
277
+ get /Keep Device Awake Screen On ([^$]+)/i do |duration|
278
+
279
+ a = duration.split.map(&:to_i)
280
+ secs = Subunit.new(units={minutes:60, hours:60, seconds: 60}, a).to_i
281
+
282
+ h = {
283
+ permanent: true, screen_option: 0, seconds_to_stay_awake_for: secs
284
+ }
285
+ [KeepAwakeAction, h]
286
+ end
287
+
288
+ #a: Disable Keep Awake
289
+ #
290
+ get /Disable Keep Awake/i do
291
+ [KeepAwakeAction, {enabled: false, screen_option: 0}]
292
+ end
293
+
294
+ #e.g a: if Airplane mode enabled
295
+ #
296
+ get /if (.*)/i do
297
+ [IfConditionAction, {}]
298
+ end
299
+
300
+ get /End If/i do
301
+ [EndIfAction, {}]
302
+ end
303
+
304
+ # -- MacroDroid Specific ------------------------------------------------
305
+ #
306
+ get /^Set Variable$/i do
307
+ [SetVariableAction, {}]
308
+ end
309
+
310
+ end
311
+
312
+ alias find_action run_route
313
+
314
+
315
+ end
316
+
317
+ class ConstraintsNlp
318
+ include AppRoutes
319
+
320
+ def initialize()
321
+
322
+ super()
323
+ params = {}
324
+ constraints(params)
325
+
326
+ end
327
+
328
+ def constraints(params)
329
+
330
+ get /^airplane mode (.*)/i do |state|
331
+ [AirplaneModeConstraint, {enabled: (state =~ /^enabled|on$/i) == 0}]
332
+ end
333
+
334
+ end
335
+
336
+ alias find_constraint run_route
337
+
338
+ end
339
+
340
+
341
+ class MacroError < Exception
342
+ end
343
+
10
344
  class Macro
11
345
  using ColouredText
12
346
  using Params
13
347
 
14
348
  attr_reader :local_variables, :triggers, :actions, :constraints,
15
349
  :guid, :deviceid
16
- attr_accessor :title, :description
350
+ attr_accessor :title, :description, :remote_url
17
351
 
18
- def initialize(name=nil, geofences: nil, deviceid: nil, debug: false)
352
+ def initialize(name=nil, geofences: nil, deviceid: nil, remote_url: nil,
353
+ debug: false)
19
354
 
20
355
  @title, @geofences, @deviceid, @debug = name, geofences, deviceid, debug
356
+ @remote_url = remote_url
21
357
 
22
358
  puts 'inside Macro#initialize' if @debug
23
359
 
24
- @local_variables, @triggers, @actions, @constraints = [], [], [], []
360
+ @local_variables, @triggers, @actions, @constraints = {}, [], [], []
25
361
  @h = {}
26
362
 
27
363
  end
@@ -48,9 +384,13 @@ class Macro
48
384
  end
49
385
 
50
386
  def to_h()
387
+
388
+ a = @local_variables.map do |k,v|
389
+ varify(k,v).to_camelcase.map{|key,value| ['m_' + key, value]}.to_h
390
+ end
51
391
 
52
392
  h = {
53
- local_variables: @local_variables,
393
+ local_variables: a,
54
394
  m_trigger_list: @triggers.map(&:to_h),
55
395
  m_action_list: @actions.map(&:to_h),
56
396
  m_category: @category,
@@ -60,7 +400,7 @@ class Macro
60
400
  m_excludeLog: false,
61
401
  m_GUID: guid(),
62
402
  m_isOrCondition: false,
63
- m_enabled: false,
403
+ m_enabled: true,
64
404
  m_descriptionOpen: false,
65
405
  m_headingColor: 0
66
406
  }
@@ -85,7 +425,7 @@ class Macro
85
425
  if h[:local_variables].any? and h[:local_variables].first.any? then
86
426
 
87
427
  @local_variables = h[:local_variables].map do |var|
88
-
428
+
89
429
  val = case var[:type]
90
430
  when 0 # boolean
91
431
  var[:boolean_value]
@@ -104,7 +444,7 @@ class Macro
104
444
 
105
445
  # fetch the triggers
106
446
  @triggers = h[:trigger_list].map do |trigger|
107
- puts 'trigger: ' + trigger.inspect
447
+ puts 'trigger: ' + trigger.inspect if @debug
108
448
  #exit
109
449
  object(trigger.to_snake_case)
110
450
 
@@ -183,27 +523,88 @@ class Macro
183
523
 
184
524
  @title = node.text('macro') || node.attributes[:name]
185
525
 
526
+ node.xpath('variable').each {|e| set_var(*e.text.to_s.split(/: */,2)) }
527
+
186
528
  #@description = node.attributes[:description]
187
529
 
188
- tp = TriggersNlp.new
530
+ tp = TriggersNlp.new(self)
189
531
 
190
- @triggers = node.xpath('trigger').map do |e|
532
+ @triggers = node.xpath('trigger').flat_map do |e|
191
533
 
192
534
  r = tp.find_trigger e.text
193
535
 
194
536
  puts 'found trigger ' + r.inspect if @debug
195
537
 
196
- if r then
197
- if r[0] == GeofenceTrigger then
198
- GeofenceTrigger.new(r[1], geofences: @geofences)
538
+ item = e.element('item')
539
+ if item then
540
+
541
+ if item.element('description') then
542
+
543
+ item.xpath('description').map do |description|
544
+
545
+ inner_lines = description.text.to_s.strip.lines
546
+ puts 'inner_lines: ' + inner_lines.inspect if @debug
547
+
548
+ trigger = if e.text.to_s.strip.empty? then
549
+ inner_lines.shift.strip
550
+ else
551
+ e.text.strip
552
+ end
553
+
554
+ puts 'trigger: ' + trigger.inspect if @debug
555
+
556
+ r = tp.find_trigger trigger
557
+ puts 'r: ' + r.inspect if @debug
558
+ #o = r[0].new([description, self]) if r
559
+ o = object_create(r[0], [description, self]) if r
560
+ puts 'after o' if @debug
561
+ o
562
+
563
+ end
564
+
199
565
  else
200
- r[0].new(r[1])
566
+
567
+ trigger = e.text.strip
568
+ r = tp.find_trigger trigger
569
+
570
+ a = e.xpath('item/*')
571
+
572
+ h = if a.any? then
573
+ a.map {|node| [node.name.to_sym, node.text.to_s]}.to_h
574
+ else
575
+ {}
576
+ end
577
+
578
+ r = tp.find_trigger trigger
579
+ #r[0].new(h) if r
580
+ if r then
581
+ object_create(r[0], h)
582
+ else
583
+ raise MacroError, 'App-routes: Trigger "' + trigger + '" not found'
584
+ end
585
+
201
586
  end
587
+
588
+ else
589
+
590
+ trigger = e.text.strip
591
+ r = tp.find_trigger trigger
592
+ #r[0].new(r[1]) if r
593
+
594
+ if r then
595
+ object_create(r[0],r[1])
596
+ else
597
+ raise MacroError, 'App-routes: Trigger "' + trigger + '" not found'
598
+ end
599
+
202
600
  end
203
601
 
602
+
204
603
  end
604
+
605
+
205
606
 
206
- ap = ActionsNlp.new
607
+ ap = ActionsNlp.new self
207
608
 
208
609
  @actions = node.xpath('action').flat_map do |e|
209
610
 
@@ -226,14 +627,22 @@ class Macro
226
627
  e.text.strip
227
628
  end
228
629
 
630
+ puts 'action: ' + action.inspect if @debug
631
+
229
632
  r = ap.find_action action
230
- r[0].new(description) if r
633
+ puts 'r: ' + r.inspect if @debug
634
+ puts 'description: ' + description.xml.inspect if @debug
635
+ #o = r[0].new([description, self]) if r
636
+ o = object_create(r[0],[description, self]) if r
637
+ puts 'after o' if @debug
638
+ o
231
639
 
232
640
  end
233
641
 
234
642
  else
235
643
 
236
644
  action = e.text.strip
645
+ puts 'action: ' + action.inspect if @debug
237
646
  r = ap.find_action action
238
647
 
239
648
  a = e.xpath('item/*')
@@ -243,9 +652,11 @@ class Macro
243
652
  else
244
653
  {}
245
654
  end
655
+ puts 'h: ' + h.inspect if @debug
246
656
 
247
- r = ap.find_action action
248
- r[0].new(h) if r
657
+ #r = ap.find_action action
658
+ #r[0].new(h.merge(macro: self)) if r
659
+ object_create(r[0], h.merge(macro: self)) if r
249
660
 
250
661
  end
251
662
 
@@ -253,7 +664,8 @@ class Macro
253
664
 
254
665
  action = e.text.strip
255
666
  r = ap.find_action action
256
- r[0].new(r[1]) if r
667
+ #r[0].new(r[1]) if r
668
+ object_create(r[0],r[1]) if r
257
669
 
258
670
  end
259
671
 
@@ -266,9 +678,7 @@ class Macro
266
678
  r = cp.find_constraint e.text
267
679
  puts 'found constraint ' + r.inspect if @debug
268
680
 
269
- if r then
270
- r[0].new(r[1])
271
- end
681
+ object_create(r[0], r[1]) if r
272
682
 
273
683
  end
274
684
 
@@ -313,6 +723,27 @@ class Macro
313
723
  def set_env()
314
724
  @triggers.each(&:set_env)
315
725
  end
726
+
727
+ def set_var(label, v='')
728
+
729
+ value = if v.to_f.to_s == v
730
+ v.to_f
731
+ elsif v.downcase == 'true'
732
+ true
733
+ elsif v.downcase == 'false'
734
+ false
735
+ elsif v.to_i.to_s == v
736
+ v.to_i
737
+ else
738
+ v
739
+ end
740
+
741
+ if not @local_variables.has_key? label.to_sym then
742
+ @local_variables.merge!({label.to_sym => value})
743
+ end
744
+
745
+ varify(label, value)
746
+ end
316
747
 
317
748
  def to_pc()
318
749
 
@@ -354,22 +785,34 @@ EOF
354
785
 
355
786
  a << vars.join("\n")
356
787
  end
788
+
789
+ puts 'before triggers' if @debug
357
790
 
358
791
  a << @triggers.map do |x|
792
+
793
+ puts 'x: ' + x.inspect if @debug
794
+ raise 'Macro#to_s trigger cannot be nil' if x.nil?
795
+
359
796
  s =-x.to_s(colour: colour)
797
+ puts 's: ' + s.inspect if @debug
360
798
 
361
799
  s2 = if s.lines.length > 1 then
362
800
  "\n" + s.lines.map {|x| x.prepend (' ' * (indent+1)) }.join
363
801
  else
364
802
  ' ' + s
365
803
  end
804
+
805
+ puts 's2: ' + s2.inspect if @debug
806
+
366
807
  #s.lines > 1 ? "\n" + x : x
367
808
  (colour ? "t".bg_red.gray.bold : 't') + ":" + s2
368
809
  end.join("\n")
369
810
 
811
+ puts 'before actions' if @debug
370
812
  actions = @actions.map do |x|
371
813
 
372
-
814
+ puts 'x: ' + x.inspect if @debug
815
+ raise 'Macro#to_s action cannot be nil' if x.nil?
373
816
  s = x.to_s(colour: colour)
374
817
  #puts 's: ' + s.inspect
375
818
 
@@ -433,6 +876,7 @@ EOF
433
876
  a << actions
434
877
 
435
878
 
879
+ puts 'before constraints' if @debug
436
880
  if @constraints.any? then
437
881
  a << @constraints.map do |x|
438
882
  (colour ? "c".bg_green.gray.bold : 'c') + ": %s" % x
@@ -494,21 +938,48 @@ EOF
494
938
 
495
939
  puts ('inside object h:' + h.inspect).debug if @debug
496
940
  klass = Object.const_get h[:class_type]
497
- puts klass.inspect.highlight if $debug
941
+ puts klass.inspect.highlight if @debug
498
942
 
499
943
  if klass == GeofenceTrigger then
500
- puts 'GeofenceTrigger found'.highlight if $debug
944
+ puts 'GeofenceTrigger found'.highlight if @debug
501
945
  GeofenceTrigger.new(h, geofences: @geofences)
502
946
  else
503
- puts 'before klass'
947
+ puts 'before klass' if @debug
504
948
  h2 = h.merge( macro: self)
505
- puts 'h2: ' + h2.inspect
949
+ puts 'h2: ' + h2.inspect if @debug
506
950
  r = klass.new h2
507
- puts 'r:' + r.inspect
951
+ puts 'r:' + r.inspect if @debug
508
952
  r
509
953
 
510
954
  end
511
955
 
512
956
  end
957
+
958
+ def object_create(klass, *args)
959
+
960
+ begin
961
+ klass.new(*args)
962
+ rescue
963
+ raise MacroError, klass.to_s + ': ' + ($!).to_s
964
+ end
965
+ end
966
+
967
+ def varify(label, value='')
968
+
969
+
970
+ type = VAR_TYPES[value.class.to_s.to_sym]
971
+
972
+ h = {
973
+ boolean_value: false,
974
+ decimal_value: 0.0,
975
+ int_value: 0,
976
+ name: label,
977
+ string_value: '',
978
+ type: type[0]
979
+ }
980
+ h[type[1]] = value
981
+ h
982
+
983
+ end
513
984
 
514
985
  end