/
ravenmasker
executable file
·212 lines (166 loc) · 6.5 KB
/
ravenmasker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#!/usr/bin/env ruby
# === === === === === === === === === === === === === === === === === === === #
# filename: ravenmasker
# === === === === === === === === === === === === === === === === === === === #
require 'thor'
require_relative 'masker'
require_relative 'raven'
class RavenMasker < Thor
# override implemented from https://stackoverflow.com/a/45730659/5103831
# this is to make sure the ordering of commands is true to the ordering here
class << self
def help(shell, subcommand = false)
list = printable_commands(true, subcommand)
Thor::Util.thor_classes_in(self).each do |klass|
list += klass.printable_commands(false)
end
# Remove this line to disable alphabetical sorting
# list.sort! { |a, b| a[0] <=> b[0] }
# Add this line to remove the help-command itself from the output
list.reject! {|l| l[0].split[1] == 'help'}
if defined?(@package_name) && @package_name
shell.say "#{@package_name} commands:"
else
shell.say "Commands:"
end
shell.print_table(list, :indent => 2, :truncate => false)
shell.say
class_options_help(shell)
# Add this line if you want to print custom text at the end of your help output.
# (similar to how Rails does it)
shell.say "Run `#{self.basename} help <command>` for a more detailed description"
end
end
AUTH_FILE = __dir__ + '/auth.json'
desc 'mask <img> <msg> <tags>', 'encrypt <msg> into <img> using <tags>'
option :output, :aliases => '-o'
long_desc <<-LONGDESC
`ravenmasker mask` will encrypt a message <msg> into a given image <img>, utilizing a provided set of tags, suitable for Twitter hashtags
e.g. ravenmasker mask image.png 'hello world' foo,bar,ravenmasker2018
It is recommended that the image be at least 128x128px to work, however the encryption is more effective for larger messages. Additionally, the image must not have any pre-existing transparency. Lastly, this will not work in the unlikely case the payload has more characters than the image has pixels, but it is recommended the payload length be no more than 1% of the image pixel count.
Tags must be provided as a comma-separated list. Valid tags have only letters, numbers, and underscores, and cannot start with a number.
An output file may be optionally provided with `--output FILENAME` or `-o FILENAME`, otherwise the default output image will be `<img>_masked.png`
LONGDESC
def mask( img_name, msg, tags )
begin
Masker.img_file_valid?( img_name )
rescue => err
@shell.say "\tERROR // Provided image file, #{img_name}, is not valid..."
@shell.say "\t#{err.message}"
return
end
begin
Masker.msg_valid?( msg )
rescue => err
@shell.say "\tERROR // Provided message, #{msg}, is not valid..."
@shell.say "\t#{err.message}"
return
end
tag_arr = tags.split( ',' )
begin
Masker.tags_valid?( tag_arr )
rescue => err
@shell.say "\tERROR // Provided tags, #{tag_arr}, are not valid..."
@shell.say "\t#{err.message}"
return
end
if options[:output]
output = options[:output]
else
output = img_name.chomp( '.png' ) + '_masked.png'
end
begin
Masker.mask( img_name, msg, tag_arr, output )
rescue ArgumentError => err
@shell.say "\tERROR // Masking process..."
@shell.say "\t#{err.message}"
end
puts "\t'#{msg}' was encrypted into #{output} using the tags #{tags.split ','}"
output
end
desc 'unmask <img> <tags>', 'decrypt a message from <img> using <tags>'
long_desc <<-LONGDESC
`ravenmasker unmask` will decrypt a message from <img>, using the given <tags> as a cipher.
e.g. ravenmasker unmask mask masked_image.png foo,bar,ravenmasker2018
Tags must be provided as a comma-separated list. Valid tags have only letters, numbers, and underscores, and cannot start with a number.
If a message is not found (either the cipher was incorrect, or the image was not encrypted to begin with), an error will be printed. Otherwise, the decrypted message will be the only output.
LONGDESC
def unmask( img_name, tags )
begin
Masker.img_file_valid?( img_name )
rescue => err
@shell.say "\tERROR // Provided image file, #{img_name}, is not valid..."
@shell.say "\t#{err.message}"
return
end
if tags.is_a? String
tag_arr = tags.split( ',' )
elsif tags.is_a? Array
tag_arr = tags
else
@shell.say "\tERROR // Provided invalid Type for tags: #{tags.class}"
return
end
begin
Masker.tags_valid?( tag_arr )
rescue => err
@shell.say "\tERROR // Provided tags, #{tag_arr}, are not valid..."
@shell.say "\t#{err.message}"
return
end
begin
msg = Masker.unmask( img_name, tag_arr )
rescue ArgumentError => err
@shell.say "\tERROR // unmasking failed..."
@shell.say "\t#{err.message}"
return
end
if msg.length == 0
msg = "\tERROR // A message could not be decrypted from #{img_name}."
end
@shell.say "\t#{msg}"
end
desc 'pull <status>', 'read <status> tweet and check for an encrypted message'
long_desc <<-LONGDESC
LONGDESC
def pull( status, output=nil )
if !File.exist? AUTH_FILE
@shell.say "\tERROR // Could not find auth file at " + AUTH_FILE
end
auth = JSON.parse( File.read( AUTH_FILE ) )
raven = Raven.new( auth )
tweet = raven.get_tweet( status )
@shell.say raven.tweet_to_s( tweet )
@shell.say # newline
if !raven.valid_tweet?( tweet )
@shell.say "\tERROR // Provided tweet does not have an attached PNG"
return
end
img_name = raven.pull_img_from_tweet( tweet, output )
tags = raven.get_tags_as_strs( tweet )
unmask( img_name, tags )
end
desc 'send <img> <msg> <tweet> <tags>', 'tweet <tweet> with <msg> encrypted into <img> using <tags>'
long_desc <<-LONGDESC
LONGDESC
def send( img_name, msg, tweet, tags )
if !File.exist? AUTH_FILE
@shell.say "\tERROR // Could not find auth file at " + AUTH_FILE
end
auth = JSON.parse( File.read( __dir__ + '/auth.json' ) )
raven = Raven.new( auth )
tweet = raven.add_tags_to_msg( tweet, tags )
@shell.say "Encypting..."
masked = mask( img_name, msg, tags )
@shell.say #newline
@shell.say """Tweeting:
Message: #{tweet}
Source Image: #{img_name}"""
begin
raven.send_tweet tweet, masked
rescue Twitter::Error::Unauthorized => err
@shell.say "\t#{err.msg}"
end
end
end
RavenMasker.start( ARGV )