ruby-macrodroid 0.8.10 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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