Create new ruby gem - Moriarty part-3

decentralizuj

decentralizuj

Posted on April 2, 2021

Create new ruby gem - Moriarty part-3

GitHub logo 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/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Don't forget to give this file executable permissions:

sudo chmod +x bin/moriarty
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

Now you can use your gem from terminal:

ruby bin/moriarti sherlock --hunt
Enter fullscreen mode Exit fullscreen mode

Or compile into gem:

bundle exec rake build
Enter fullscreen mode Exit fullscreen mode

To push into rubygems:

# register on rubygems

gem push pkg/moriarti-0.1.0.gem
Enter fullscreen mode Exit fullscreen mode

Or upload to github and use from git:

# Gemfile of some app

require 'moriarty', '~> 0.1.0', git: 'https:github.com/....'
Enter fullscreen mode Exit fullscreen mode

Next article will be about refactoring gem and terminal use (options, colors, output...)

💖 💪 🙅 🚩
decentralizuj
decentralizuj

Posted on April 2, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related