decentralizuj / moriarty
Moriarty - Tool to check social networks for available username
In past series I created moriarti.rb
(responsible for everything). I love to work with ruby scripts, because of ability to write powerful/useful code in short time. But this only works for "small projects". If we want to extend script after some time, It'll become pain in the ass. For this we have ruby-gems, which are easily extendible even for different developer.
- Performance advice:
Each require make ruby slower. Do not create gem with a few lines of code and many requires. For that case, make a script file and use it until you want to extend it.
Create Ruby Gem
First thing is to create directory where you want your ruby gem. In root directory create bin
and lib
folders. That's same for every gem.
# Moriarty directory tree moriarty | | - bin/ | | - lib/
Lib folder handle our scripts, and bin (binaries) handle executable files. Now let's go back to the root of our gem. We need few more files to add there.
# Moriarty root files Gemfile Rakefile README.md # important! moriarty.gemspec
Let's talk about README file first. It's important to create and to write few lines about gem. Even if you do not plan to opensource it, or share with others, after some time you'll forget what you did 6 month ago in which file. To avoid time and nerve lose, don't be lazy and create it. Same thing with comments in source code - it's not must to go in details, just something to help you remember those details.
Pro TIP:
Too many comments make source code unreadable. make comments short, write code to be readable. I wrote all this comments for purpose of learning. If you want to add comments in right way, learn RDoc. I will write another article about generating RDoc from gem's comments.
Gemfile handle our dependencies from .gemspec
file, so bundler know what to install:
# Moriarty Gemfile source 'https://rubygems.org' # this line tell bundler where to look gemspec
Gemspec file contain everything about our gem. Dependencies, name, description, version, site... but also all files that our gem use. Specification also have data about executable files, so when we install our gem locally, we can run it moriarty USERNAME
instead of ruby bin/moriarty
from root directory.
This is a reason why all objects should define path when work with files, because working directory is not always script directory.
# Gemspec file (moriarty.gemspec) # frozen_string_literal: true Gem::Specification.new do |s| s.name = 'name' s.version = '0.1.0' s.summary = 'short description' s.description = <<~DESC # Write long description here. DESC s.authors = ['...'] s.homepage = '...' s.license = '...' s.metadata['homepage_uri'] = '...' s.metadata['source_code_uri'] = '...' s.metadata['bug_tracker_uri'] = '...' # here you add all files from your gem s.files = ['README.md', 'LICENSE'] # directory with executable s.bindir = 'bin' # one or more executable files s.executables = ['...'] # directory with our files s.require_paths = ['lib'] # define gems needed to work s.add_runtime_dependency 'colorize', '~> 0.8.1' s.add_runtime_dependency 'rest-client', '~> 2.1.0' s.add_runtime_dependency 'nokogiri', '~> 1.11.2' # define gems needed for development s.add_development_dependency 'bundler', '~> 2.2.9' s.add_development_dependency 'rake', '~> 13.0.3' end
With all of this, we're ready to create files that will handle our code. In lib/
we will create file moriarty.rb
, and that file will require all other files. This way our executable (or other gem) need to require just one file for everything to work.
# lib/moriarty.rb #!/usr/bin/env ruby require 'rest-client' require 'colorize' require 'nokogiri' require_relative 'moriarty/cli'
Here I required dependencies from gemspec. I explained in first article why I use rest-client. I also required relative file moriarti/cli, and we need to create it. Don't think about it too much for now.
From our script file I will copy almost all methods. I will not copy #find!
and code we used to run script. Only code to construct username, url, send request and receive response.
# # Main class to get data and execute request # # Methods: [ #new, #go, #success?, #make_url, #url=, #user= ] # # Attributes: # :user = :moriarty => 'moriarty' # :url = 'example.com' => 'https://example.com/' # :response => [.code, .headers, .body] -> restclient#get # :html => scrapped HTML if success? -> nokogiri#html # class Moriarty attr_reader :url, :user, :response, :html # Set username and site for search request # exclude 'https', #make_url will add it for you # To use different protocol, set it as third parameter # # @jim = Moriarty.new( 'moriarty', 'example.com', :http ) # => @jim.user == 'moriarty' # => @jim.url == 'http://example.com/' def initialize( name = '', site = 'github.com', type = :https ) @user = name.to_s @url = make_url site, type end # execute request (args are optional) # @jim.go site: 'github.com', user: 'mynickname' # => true/false # -> @jim.response (.code, .headers, .body) # -> @jim.html (page HTML if request success) def go( opt = {} ) opt[:user] ||= @user url = opt[:site].nil? ? @url : make_url(opt[:site]) uri = url + opt[:user] @response = RestClient.get uri @html = Nokogiri::HTML @response return @success = true rescue return @success = false end alias search go # create URL from site name, add 'https' if needed # @jim.make_url 'instagram.com' # => 'https://instagram.com/' def make_url( link, prot = :https ) prot = nil if link.to_s.start_with? prot.to_s url = prot.nil? ? link.to_s : prot.to_s + '://' + link.to_s url += '/' unless url.end_with?('/') return url end # Set URL from site name and protocol(optional) # @jim.url = 'github.com' # => @jim.url == 'https://github.com' def url=( link, start = :https ) @url = make_url link, start end # Set username from string or :symbol # @jim.user = :moriarty # => @jim.user == 'moriarty' def user=( name ) @user = name.to_s end # Check does request succeeded or not # @jim.success? # => true/false def success? @success == true end end
Now it's time to create lib/moriarty
directory, and cli.rb
inside. That file I will use for methods we need in CLI (in future versions, that will be removed).
class Moriarty class << self # Moriarty.find! 'smartuser' # => [FOUND!] if username 'smartuser' is free on github # # Moriarty.find! :stupiduser, 'facebook.com', :hunt # => [FREE!] if user 'stupiduser' is registered on facebook def find!( username, web = 'github.com', type = :search ) @jim = Moriarty.new username.to_s, web.to_s @jim.go name = print_name(username).to_s site = print_url(web).to_s case when type.to_sym == :hunt && @jim.success? p1('+') p1('FOUND!') p2(" #{name}", :cyan, :bold) p2(" found on >> ") puts p2(site, :cyan, :bold) when type.to_sym == :hunt && !@jim.success? p1('-', :red, :red) p2(" #{name} fail on ", :red) puts p2(site, :red) when @jim.success? p1('-', :red, :red) p2(" #{name} is taken on ", :red) puts p2(site, :red) else p1('+') p1('FREE!') p2(" #{name}", :cyan, :bold) p2(" is free on >> ") puts p2(site, :cyan, :bold) end end # #hunt! is alias for #find! with :hunt option # Check is user 'stupiduser' registered on instagram # -> Moriarty.hunt! 'stupiduser', 'instagram.com' def hunt!( name, site = 'github.com', type = :hunt) find! name, site, type end # Remove extensions from domain name # Moriarty.print_url('https://www.github.com') # => 'github' def print_url( site ) site, name = site.to_s, '' if site.start_with?('http') site.gsub!("https://", '') or site.gsub!("http://", '') end site.gsub!("www.", '') if site.start_with?('www.') ext = site.to_s.split('.').last name = site.gsub(".#{ext}", '') name = name.split('/').first if ext.size < 5 return name.capitalize end # Remove extensions from username # Moriarty.print_name('@moriarty') # => 'moriarty' def print_name( name ) name.gsub!('@','') if name.to_s.start_with?('@') name.gsub!('#','') if name.to_s.start_with?('#') name.gsub!('/u/','') if name.to_s.start_with?('/u/') return name end def p1( title, color1 = :'light_green', color2 = :cyan, type = :bold ) str = ' ['.colorize(color2) + title.to_s.colorize(color1) + ']'.colorize(color2) str = str.bold if type == :bold print str end def p2( title, color = :cyan, type = :regular) str = title.colorize(color) str = str.bold if type == :bold print str end end end
In cli.rb
we again define class Moriarty
, but this time accept only self
methods. Note that you can create as many files as you want, just require them in main lib/moriarty.rb
, and add in .gemspec
.
Now we have our classes, but we need a way to execute it in terminal. For that we will create bin/moriarty
:
#!/usr/bin/env ruby # require 'lib/moriarty', which will require 'moriarti/cli' require_relative '../lib/moriarty' # Simple way to try first version # accept '--hunt' as argument hunt = ARGV.include?('--hunt') ? :hunt : :search message = hunt == :hunt ? "Starting hunt on user -> " : "Starting search for free username -> " # If no args, or include '-h' or '--help', print banner # If use '--hunt', do not search for '--hunt' username if ARGV.empty? or ARGV.include? '-h' or ARGV.include? '--help' puts "\nSearch social networks for free username".cyan puts " $ ".white + "ruby bin/moriarty sherlock watson".green puts "\nRun with --hunt to search for registered users".cyan puts " $ ".white + "ruby bin/moriarty sherlock watson --hunt\n".green exit(1) else system 'clear' or system 'cls' start_time = Time.now.to_i ARGV.each do |user| next if user == '--hunt' puts Moriarty.p2(message, :cyan, :bold) Moriarty.p1(user) puts; puts Moriarty.find! user, 'github.com', hunt Moriarty.find! user, 'rubygems.org/profiles', hunt Moriarty.find! "@#{user}", 'dev.to', hunt Moriarty.find! "@#{user}", 'medium.com', hunt Moriarty.find! "/u/#{user}", 'reddit.com', hunt Moriarty.find! user, 'youtube.com', hunt Moriarty.find! user, 'facebook.com', hunt Moriarty.find! user, 'instagram.com', hunt Moriarty.find! "@#{user}", 'tiktok.com', hunt end end_time = Time.now.to_i counter = end_time - start_time sec = ' second' sec += 's' unless counter.to_s.end_with?('1') puts Moriarty.p2('Finished in ->', :cyan, :bold) Moriarty.p1(counter) Moriarty.p2(sec, :cyan, :bold) puts; puts end
Don't forget to give this file executable permissions:
sudo chmod +x bin/moriarty
Almost done! We need to add all those files in .gemspec
:
s.files = ['bin/moriarty', 'lib/moriarty.rb', 'lib/moriarty/cli.rb', 'moriarty.gemspec', 'README.md', 'LICENSE'] s.bindir = 'bin' s.executables = ['moriarty']
I added license file here (MIT in my case), that's all up to you. The easiest way is to create repository on GitHub and choose license.
To be able to use rake
tasks, open Rakefile
and add:
require 'bundler/gem_tasks'
Now you can use your gem from terminal:
ruby bin/moriarti sherlock --hunt
Or compile into gem:
bundle exec rake build
To push into rubygems:
# register on rubygems gem push pkg/moriarti-0.1.0.gem
Or upload to github and use from git:
# Gemfile of some app require 'moriarty', '~> 0.1.0', git: 'https:github.com/....'
Next article will be about refactoring gem and terminal use (options, colors, output...)
Top comments (0)