text:
# openssl-info
# Autogenerated from man page /usr/share/man/man1/openssl-info.1ssl.gz
complete -c openssl-info -o help -d 'Print out a usage message'
complete -c openssl-info -o configdir -d 'Outputs the default directory for OpenSSL configuration files'
complete -c openssl-info -o enginesdir -d 'Outputs the default directory for OpenSSL engine modules'
complete -c openssl-info -o modulesdir -d 'Outputs the default directory for OpenSSL dynamically loadable modules other …'
complete -c openssl-info -o dsoext -d 'Outputs the \\s-1DSO\\s0 extension OpenSSL uses'
complete -c openssl-info -o dirnamesep -d 'Outputs the separator character between a directory specification and a filen…'
complete -c openssl-info -o listsep -d 'Outputs the OpenSSL list separator character'
complete -c openssl-info -o seeds -d 'Outputs the randomness seed sources'


text:
# unzip
# Autogenerated from man page /usr/share/man/man1/unzip.1.gz
complete -c unzip -s Z -d 'zipinfo(1) mode'
complete -c unzip -s A -d '[OS/2, Unix DLL] print extended help for the DLL\'s programming interface (API)'
complete -c unzip -s c -d 'extract files to stdout/screen (``CRT\'\')'
complete -c unzip -s f -d 'freshen existing files, i. e'
complete -c unzip -s l -d 'list archive files (short format)'
complete -c unzip -s p -d 'extract files to pipe (stdout)'
complete -c unzip -s t -d 'test archive files'
complete -c unzip -s T -d '[most OSes] set the timestamp on the archive(s) to that of the newest file in…'
complete -c unzip -s u -d 'update existing files and create new ones if needed'
complete -c unzip -s v -d 'list archive files (verbose format) or show diagnostic version info'
complete -c unzip -s z -d 'display only the archive comment.  MODIFIERS'
complete -c unzip -s a -d 'convert text files'
complete -c unzip -s b -d '[general] treat all files as binary (no text conversions)'
complete -c unzip -s B -d '[when compiled with UNIXBACKUP defined] save a backup copy of each overwritte…'
complete -c unzip -s C -d 'use case-insensitive matching for the selection of archive entries from the c…'
complete -c unzip -s D -d 'skip restoration of timestamps for extracted items'
complete -c unzip -s E -d '[MacOS only] display contents of MacOS extra field during restore operation'
complete -c unzip -s F -d '[Acorn only] suppress removal of NFS filetype extension from stored filenames'
complete -c unzip -s i -d '[MacOS only] ignore filenames stored in MacOS extra fields'
complete -c unzip -s j -d 'junk paths'
complete -c unzip -s J -d '[BeOS only] junk file attributes'
complete -c unzip -s K -d '[AtheOS, BeOS, Unix only] retain SUID/SGID/Tacky file attributes'
complete -c unzip -s L -d 'convert to lowercase any filename originating on an uppercase-only operating …'
complete -c unzip -s M -d 'pipe all output through an internal pager similar to the Unix more(1) command'
complete -c unzip -s n -d 'never overwrite existing files'
complete -c unzip -s N -d '[Amiga] extract file comments as Amiga filenotes'
complete -c unzip -s o -d 'overwrite existing files without prompting'
complete -c unzip -s P -d 'use password to decrypt encrypted zipfile entries (if any)'
complete -c unzip -s q -d 'perform operations quietly (-qq = even quieter)'
complete -c unzip -s s -d '[OS/2, NT, MS-DOS] convert spaces in filenames to underscores'
complete -c unzip -s S -d '[VMS] convert text files (-a, -aa) into Stream_LF record format, instead of t…'
complete -c unzip -s U -d '[UNICODE_SUPPORT only] modify or disable UTF-8 handling'
complete -c unzip -s V -d 'retain (VMS) file version numbers'
complete -c unzip -s W -d '[only when WILD_STOP_AT_DIR compile-time option enabled] modifies the pattern…'
complete -c unzip -s X -d '[VMS, Unix, OS/2, NT, Tandem] restore owner/protection info (UICs and ACL ent…'
complete -c unzip -s Y -d '[VMS] treat archived file name endings of ``'
complete -c unzip -s '$'
complete -c unzip -s / -d '[Acorn only] overrides the extension list supplied by Unzip$Ext environment v…'
complete -c unzip -s '^' -d '[Unix only] allow control characters in names of extracted ZIP archive entries'
complete -c unzip -s 2 -d '[VMS] force unconditionally conversion of file names to ODS2-compatible names'

repo_language: Shell

mime_type: text/plain


text:
# badblocks
# Autogenerated from man page /usr/share/man/man8/badblocks.8.gz
complete -c badblocks -s b -d 'Specify the size of blocks in bytes.   The default is 1024'
complete -c badblocks -s c -d 'is the number of blocks which are tested at a time.   The default is 64'
complete -c badblocks -s d -d 'This parameter, if passed and non-zero, will cause bad blocks to sleep betwee…'
complete -c badblocks -s e -d 'Specify a maximum number of bad blocks before aborting the test'
complete -c badblocks -s f -d 'Normally, badblocks will refuse to do a read/write or a non-destructive test …'
complete -c badblocks -s i -d 'Read a list of already existing known bad blocks'
complete -c badblocks -s n -d 'Use non-destructive read-write mode'
complete -c badblocks -s o -d 'Write the list of bad blocks to the specified file'
complete -c badblocks -s p -d 'Repeat scanning the disk until there are no new blocks discovered in num_pass…'
complete -c badblocks -s s -d 'Show the progress of the scan by writing out rough percentage completion of t…'
complete -c badblocks -s t -d 'Specify a test pattern to be read (and written) to disk blocks'
complete -c badblocks -s v -d 'Verbose mode'
complete -c badblocks -s w -d 'Use write-mode test'
complete -c badblocks -s B -d 'Use buffered I/O and do not use Direct I/O, even if it is available'
complete -c badblocks -s l -d 'option in e2fsck (8) or mke2fs (8)'
complete -c badblocks -s X -d 'Internal flag only to be used by e2fsck (8) and mke2fs (8)'


text:
# chroot
# Autogenerated from man page /usr/share/man/man8/chroot.8.gz
complete -c chroot -l groups -d 'specify supplementary groups as g1,g2,. ,gN'
complete -c chroot -l userspec -d 'specify user and group (ID or name) to use'
complete -c chroot -l skip-chdir -d 'do not change working directory to \'/\''
complete -c chroot -l help -d 'display this help and exit'
complete -c chroot -l version -d 'output version information and exit'


text:
# isaset
# Autogenerated from man page /usr/share/man/man8/isaset.8.gz
complete -c isaset -s f -d 'Enable flat address space mode'
complete -c isaset -s y -d 'Disable interactive mode'
complete -c isaset -s W -d 'Perform a 16-bit write'
complete -c isaset -s L -d 'Perform a 32-bit write'


text:
# foo2hiperc
# Autogenerated from man page /usr/share/man/man1/foo2hiperc.1.gz
complete -c foo2hiperc -s c -d 'Force color mode if autodetect doesn\'t work'
complete -c foo2hiperc -s d -d 'Duplex code to send to printer [1]'
complete -c foo2hiperc -s g -d 'Set page dimensions in pixels [5100x6600]'
complete -c foo2hiperc -s m -d 'Media code to send to printer [0]'
complete -c foo2hiperc -s p -d 'Paper code to send to printer [2]'
complete -c foo2hiperc -s n -d 'Number of copies [1]'
complete -c foo2hiperc -s r -d 'Set device resolution in pixels/inch [600x600]'
complete -c foo2hiperc -s s -d 'Source (InputSlot) code to send to printer [0]'
complete -c foo2hiperc -s t -d 'Draft mode.   Every other pixel is white'
complete -c foo2hiperc -s J -d 'Filename string to send to printer'
complete -c foo2hiperc -s U -d 'Username string to send to printer'
complete -c foo2hiperc -s u -d 'Set the offset of the start of the printable region from the upper left corne…'
complete -c foo2hiperc -s l -d 'Set the offset of the end of the printable region from the lower right corner…'
complete -c foo2hiperc -s L -d 'Send logical clipping amounts implied by -u/-l in the HIPERC stream [3]'
complete -c foo2hiperc -s A -d 'AllIsBlack: convert C=1,M=1,Y=1 to just K=1.   Works with bitcmyk input only'
complete -c foo2hiperc -s B -d 'BlackClears: K=1 forces C,M,Y to 0.   Works with bitcmyk input only'
complete -c foo2hiperc -s Z -d 'Use uncompressed (0) or compressed (1) JBIG data'
complete -c foo2hiperc -s S -d 'Output just a single color plane from a color print and print it on the black…'
complete -c foo2hiperc -s D -d 'Set Debug level [0]'


text:
# netplan-apply
# Autogenerated from man page /usr/share/man/man8/netplan-apply.8.gz
complete -c netplan-apply -s h -d 'Print basic help'


text:
# rpcgen
# Autogenerated from man page /usr/share/man/man1/rpcgen.1.gz
complete -c rpcgen -s T -d 'option, it will also generate the RPC dispatch table in proto_tbl. i'
complete -c rpcgen -o Sc -d 'option, it will also generate  sample code which would illustrate how to use …'
complete -c rpcgen -o Ss -d 'option, it will also generate a sample server code which would illustrate how…'
complete -c rpcgen -s s -d 'option, it creates servers for that particular class of transports'
complete -c rpcgen -s n -d 'option, it creates a server for the transport specified by netid'
complete -c rpcgen -s a -d 'Generate all the files including sample code for client and server side'
complete -c rpcgen -s b -d 'This generates code for the SunOS4. 1 style of rpc'
complete -c rpcgen -s 5 -d 'This generates code for the SysVr4 style of rpc'
complete -c rpcgen -s c -d 'Compile into XDR routines'
complete -c rpcgen -s C -d 'Generate code in ANSI C'
complete -c rpcgen -s k -d 'Generate code in K&R C.   The default is ANSI C'
complete -c rpcgen -o Dname -d 'Define a symbol name.  Equivalent to the #define directive in the source'
complete -c rpcgen -s h -d 'Compile into C data-definitions (a header file)'
complete -c rpcgen -s I -d 'Generate a service that can be started from inetd'
complete -c rpcgen -s K -d 'By default, services created using rpcgen wait 120 seconds after servicing a …'
complete -c rpcgen -s 1 -d 'When monitoring for a server, some portmonitors, like listen(1M), always spaw…'
complete -c rpcgen -s l -d 'Compile into client-side stubs'
complete -c rpcgen -s m -d 'Compile into server-side stubs, but do not generate a “main” routine'
complete -c rpcgen -s M -d 'Generate multithread-safe stubs for passing arguments and results between rpc…'
complete -c rpcgen -s N -d 'Use the newstyle of rpcgen'
complete -c rpcgen -s o -d 'Specify the name of the output file'
complete -c rpcgen -o Sm
complete -c rpcgen -s t -d 'modes only)'
complete -c rpcgen -s D -d and
complete -c rpcgen -o DTEST -d 'server side stubs for  all the transport belonging to the class datagram_n to…'


text:
# systemd-journald-varlink@
# Autogenerated from man page /usr/share/man/man8/systemd-journald-varlink@.socket.8.gz
complete -c systemd-journald-varlink@ -l namespace -d 'switch of journalctl(1) may be used to view the log stream of a specific name…'


text:
# openssl-version
# Autogenerated from man page /usr/share/man/man1/openssl-version.1ssl.gz
complete -c openssl-version -o help -d 'Print out a usage message'
complete -c openssl-version -s a -d 'All information, this is the same as setting all the other flags'
complete -c openssl-version -s v -d 'The current OpenSSL version'
complete -c openssl-version -s b -d 'The date the current version of OpenSSL was built'
complete -c openssl-version -s o -d 'Option information: various options set when the library was built'
complete -c openssl-version -s f -d 'Compilation flags'
complete -c openssl-version -s p -d 'Platform setting'
complete -c openssl-version -s d -d '\\s-1OPENSSLDIR\\s0 setting'
complete -c openssl-version -s e -d '\\s-1ENGINESDIR\\s0 settings'
complete -c openssl-version -s m -d '\\s-1MODULESDIR\\s0 settings'
complete -c openssl-version -s r -d 'The random number generator source settings'


text:
# pdfattach
# Autogenerated from man page /usr/share/man/man1/pdfattach.1.gz
complete -c pdfattach -o replace -d 'Replace embedded file with same name (if it exists)'
complete -c pdfattach -s v -d 'Print copyright and version information'
complete -c pdfattach -s h -d 'Print usage information.  ( -help and'
complete -c pdfattach -l help -d 'are equivalent. ) EXIT CODES'


text:
# bzcat
# Autogenerated from man page /usr/share/man/man1/bzcat.1.gz
complete -c bzcat -s c -l stdout -d 'Compress or decompress to standard output'
complete -c bzcat -s d -l decompress -d 'Force decompression'
complete -c bzcat -s z -l compress -d 'The complement to -d: forces compression, regardless of the invocation name'
complete -c bzcat -s t -l test -d 'Check integrity of the specified file(s), but don\'t decompress them'
complete -c bzcat -s f -l force -d 'Force overwrite of output files'
complete -c bzcat -s k -l keep -d 'Keep (don\'t delete) input files during compression or decompression'
complete -c bzcat -s s -l small -d 'Reduce memory usage, for compression, decompression and testing'
complete -c bzcat -s q -l quiet -d 'Suppress non-essential warning messages'
complete -c bzcat -s v -l verbose -d 'Verbose mode -- show the compression ratio for each file processed'
complete -c bzcat -s h -l help -d 'Print a help message and exit'
complete -c bzcat -s L -l license -s V -l version -d 'Display the software version, license terms and conditions'
complete -c bzcat -s 1 -l fast -s 9 -l best -d 'Set the block size to 100 k, 200 k .   900 k when compressing'
complete -c bzcat -l repetitive-fast -l repetitive-best -d 'These flags are redundant in versions 0. 9. 5 and above'
complete -c bzcat -o vvvv


text:
# openssl
# Autogenerated from man page /usr/share/man/man1/openssl.1ssl.gz
complete -c openssl -o help -d 'Provides a terse summary of all options'
complete -c openssl -o rand -d 'A file or files containing random data used to seed the random number generat…'
complete -c openssl -o writerand -d 'Writes the seed data to the specified file upon exit'
complete -c openssl -o ssl3 -o tls1 -o tls1_1 -o tls1_2 -o tls1_3 -o no_ssl3 -o no_tls1 -o no_tls1_1 -o no_tls1_2 -o no_tls1_3 -d 'These options require or disable the use of the specified \\s-1SSL\\s0 or \\s-1T…'
complete -c openssl -o dtls -o dtls1 -o dtls1_2 -d 'These options specify to use \\s-1DTLS\\s0 instead of \\s-1DLTS'
complete -c openssl -o engine -d 'Load the engine identified by id and use all the methods it implements (algor…'
complete -c openssl -o provider -d 'Load and initialize the provider identified by name'
complete -c openssl -o provider-path -d 'Specifies the search path that is to be used for looking for providers'


text:
# pidof
# Autogenerated from man page /usr/share/man/man8/pidof.8.gz
complete -c pidof -s s -d 'Single shot - this instructs the program to only return one pid'
complete -c pidof -s c -d 'Only return process PIDs that are running with the same root directory'
complete -c pidof -s n -d 'Avoid  stat (2) system function call on all binaries which are located on net…'
complete -c pidof -s q -d 'Do not display matched PIDs to standard out'
complete -c pidof -s x -d 'Scripts too - this causes the program to also return process id\'s of shells r…'
complete -c pidof -s z -d 'Try to detect processes which are stuck in zombie (Z) status'
complete -c pidof -s d -d 'Tells pidof to use sep as an output separator if more than one PID is shown'
complete -c pidof -s o -d 'Tells pidof to omit processes with that process id'


text:
# systemd-journald-audit
# Autogenerated from man page /usr/share/man/man8/systemd-journald-audit.socket.8.gz
complete -c systemd-journald-audit -l namespace -d 'switch of journalctl(1) may be used to view the log stream of a specific name…'


text:
# fc-validate
# Autogenerated from man page /usr/share/man/man1/fc-validate.1.gz
complete -c fc-validate -s i -d 'Only query face indexed index of each file'
complete -c fc-validate -s l -d 'Set lang as a language instead of current locale.  this is used for -m'
complete -c fc-validate -s v -d 'Show more detailed information'
complete -c fc-validate -s V -d 'Show version of the program and exit'
complete -c fc-validate -s h -d 'Show summary of options'


text:
# This file contains fish universal variable definitions.
# VERSION: 3.0
SETUVAR __fish_initialized:3100
SETUVAR fish_color_autosuggestion:555\x1ebrblack
SETUVAR fish_color_cancel:\x2dr
SETUVAR fish_color_command:005fd7
SETUVAR fish_color_comment:990000
SETUVAR fish_color_cwd:green
SETUVAR fish_color_cwd_root:red
SETUVAR fish_color_end:009900
SETUVAR fish_color_error:ff0000
SETUVAR fish_color_escape:00a6b2
SETUVAR fish_color_history_current:\x2d\x2dbold
SETUVAR fish_color_host:normal
SETUVAR fish_color_host_remote:yellow
SETUVAR fish_color_normal:normal
SETUVAR fish_color_operator:00a6b2
SETUVAR fish_color_param:00afff
SETUVAR fish_color_quote:999900
SETUVAR fish_color_redirection:00afff
SETUVAR fish_color_search_match:bryellow\x1e\x2d\x2dbackground\x3dbrblack
SETUVAR fish_color_selection:white\x1e\x2d\x2dbold\x1e\x2d\x2dbackground\x3dbrblack
SETUVAR fish_color_status:red
SETUVAR fish_color_user:brgreen
SETUVAR fish_color_valid_path:\x2d\x2dunderline
SETUVAR fish_key_bindings:fish_default_key_bindings
SETUVAR fish_pager_color_completion:\x1d
SETUVAR fish_pager_color_description:B3A06D\x1eyellow
SETUVAR fish_pager_color_prefix:normal\x1e\x2d\x2dbold\x1e\x2d\x2dunderline
SETUVAR fish_pager_color_progress:brwhite\x1e\x2d\x2dbackground\x3dcyan


text:
if status is-interactive
    # Commands to run in interactive sessions can go here
end


text:
function fish_greeting

	alias uwu='sudo'

	if string match -q "xterm-kitty" $TERM
  		neofetch  --backend kitty --source /home/$USER/.config/neofetch/neofetch-logo.png
	else
  		neofetch
	end
end


text:
# Path to Oh My Fish install.
set -q XDG_DATA_HOME
  and set -gx OMF_PATH "$XDG_DATA_HOME/omf"
  or set -gx OMF_PATH "$HOME/.local/share/omf"

# Load Oh My Fish configuration.
source $OMF_PATH/init.fish


text:
#!/usr/bin/env xdg-open
[Desktop Entry]
Name=UwUntu Welcome
GenericName=budgie-welcome
Comment=Stupidly simple.
Categories=Utility;
Type=Application
Exec=python3 /usr/bin/uwuntu-welcome/budgie-welcome
Icon=/usr/share/icons/rounded-logo.png
Terminal=false
NoDisplay=false
Name[en_US]=welcome.desktop


text:
#!/usr/bin/env xdg-open

[Desktop Entry]
Name=UwUntu Welcome Remover
GenericName=budgie-welcome
Comment=Stupidly simple.
Categories=Utility;
Type=Application
Exec=bash /usr/bin/uwuntu-welcome/removewelcome.sh
Icon=/usr/share/icons/rounded-logo.png
Terminal=false
NoDisplay=false
Name[en_US]=removewelcome.desktop


text:
theme default
theme lambda


text: lambda


text: stable


text:
\033[1;35m
                         ^7??!.
               ..::^^:: :55555?
            ^. ^???????!.!JYY?^
          ~YGP^ ^777777?7~^^^^.
        .JGBGBG^ ......:~7????7^
        ?BGGGP~           ^7????^
   ^!7!~:7GGG:             :7777!.
  :?????~ PB5               .....
   ^!7!~:7GGG:             ~YYYYJ.
        ?BGGGP~           ~Y5555!
        .JBBGBG^ ::...:^7Y5555Y~
          ~YGP^ ~YYYYYY5Y7!!!!:
            ^. !5555555?:?PPPY^
               .:^^~~~^ ^BBBBBY
                         ~JYY7.


text:
# See this wiki page for more info:
# https://github.com/dylanaraps/neofetch/wiki/Customizing-Info
print_info() {
    info title
    info underline

    info "OS" distro
    info "Uptime" uptime
    info "Shell" shell
    info "DE" de
    info "WM Theme" wm_theme
    info "Theme" theme
    info "Icons" icons
    info "Terminal" term
    info "Terminal Font" term_font
    info "CPU" cpu
    info "GPU" gpu
    info "Memory" memory

    # info "GPU Driver" gpu_driver  # Linux/macOS only
     info "CPU Usage" cpu_usage
    # info "Disk" disk
    # info "Battery" battery
    # info "Font" font
    # info "Song" song
    # [[ "$player" ]] && prin "Music Player" "$player"
     info "Local IP" local_ip
    # info "Public IP" public_ip
    # info "Users" users
    # info "Locale" locale  # This only works on glibc systems.

    #info cols

}

# Title

# Hide/Show Fully qualified domain name.
#
# Default:  'off'
# Values:   'on', 'off'
# Flag:     --title_fqdn
title_fqdn="off"

# Kernel

# Shorten the output of the kernel function.
#
# Default:  'on'
# Values:   'on', 'off'
# Flag:     --kernel_shorthand
# Supports: Everything except *BSDs (except PacBSD and PC-BSD)
#
# Example:
# on:  '4.8.9-1-ARCH'
# off: 'Linux 4.8.9-1-ARCH'
kernel_shorthand="on"

# Distro

# Shorten the output of the distro function
#
# Default:  'off'
# Values:   'on', 'tiny', 'off'
# Flag:     --distro_shorthand
# Supports: Everything except Windows and Haiku
distro_shorthand="off"

# Show/Hide OS Architecture.
# Show 'x86_64', 'x86' and etc in 'Distro:' output.
#
# Default: 'on'
# Values:  'on', 'off'
# Flag:    --os_arch
#
# Example:
# on:  'Arch Linux x86_64'
# off: 'Arch Linux'
os_arch="on"

# Uptime

# Shorten the output of the uptime function
#
# Default: 'on'
# Values:  'on', 'tiny', 'off'
# Flag:    --uptime_shorthand
#
# Example:
# on:   '2 days, 10 hours, 3 mins'
# tiny: '2d 10h 3m'
# off:  '2 days, 10 hours, 3 minutes'
uptime_shorthand="on"

# Memory

# Show memory pecentage in output.
#
# Default: 'off'
# Values:  'on', 'off'
# Flag:    --memory_percent
#
# Example:
# on:   '1801MiB / 7881MiB (22%)'
# off:  '1801MiB / 7881MiB'
memory_percent="off"

# Change memory output unit.
#
# Default: 'mib'
# Values:  'kib', 'mib', 'gib'
# Flag:    --memory_unit
#
# Example:
# kib  '1020928KiB / 7117824KiB'
# mib  '1042MiB / 6951MiB'
# gib: ' 0.98GiB / 6.79GiB'
memory_unit="mib"

# Packages

# Show/Hide Package Manager names.
#
# Default: 'tiny'
# Values:  'on', 'tiny' 'off'
# Flag:    --package_managers
#
# Example:
# on:   '998 (pacman), 8 (flatpak), 4 (snap)'
# tiny: '908 (pacman, flatpak, snap)'
# off:  '908'
package_managers="on"

# Shell

# Show the path to $SHELL
#
# Default: 'off'
# Values:  'on', 'off'
# Flag:    --shell_path
#
# Example:
# on:  '/bin/bash'
# off: 'bash'
shell_path="off"

# Show $SHELL version
#
# Default: 'on'
# Values:  'on', 'off'
# Flag:    --shell_version
#
# Example:
# on:  'bash 4.4.5'
# off: 'bash'
shell_version="on"

# CPU

# CPU speed type
#
# Default: 'bios_limit'
# Values: 'scaling_cur_freq', 'scaling_min_freq', 'scaling_max_freq', 'bios_limit'.
# Flag:    --speed_type
# Supports: Linux with 'cpufreq'
# NOTE: Any file in '/sys/devices/system/cpu/cpu0/cpufreq' can be used as a value.
speed_type="bios_limit"

# CPU speed shorthand
#
# Default: 'off'
# Values: 'on', 'off'.
# Flag:    --speed_shorthand
# NOTE: This flag is not supported in systems with CPU speed less than 1 GHz
#
# Example:
# on:    'i7-6500U (4) @ 3.1GHz'
# off:   'i7-6500U (4) @ 3.100GHz'
speed_shorthand="off"

# Enable/Disable CPU brand in output.
#
# Default: 'on'
# Values:  'on', 'off'
# Flag:    --cpu_brand
#
# Example:
# on:   'Intel i7-6500U'
# off:  'i7-6500U (4)'
cpu_brand="on"

# CPU Speed
# Hide/Show CPU speed.
#
# Default: 'on'
# Values:  'on', 'off'
# Flag:    --cpu_speed
#
# Example:
# on:  'Intel i7-6500U (4) @ 3.1GHz'
# off: 'Intel i7-6500U (4)'
cpu_speed="on"

# CPU Cores
# Display CPU cores in output
#
# Default: 'logical'
# Values:  'logical', 'physical', 'off'
# Flag:    --cpu_cores
# Support: 'physical' doesn't work on BSD.
#
# Example:
# logical:  'Intel i7-6500U (4) @ 3.1GHz' (All virtual cores)
# physical: 'Intel i7-6500U (2) @ 3.1GHz' (All physical cores)
# off:      'Intel i7-6500U @ 3.1GHz'
cpu_cores="logical"

# CPU Temperature
# Hide/Show CPU temperature.
# Note the temperature is added to the regular CPU function.
#
# Default: 'off'
# Values:  'C', 'F', 'off'
# Flag:    --cpu_temp
# Supports: Linux, BSD
# NOTE: For FreeBSD and NetBSD-based systems, you'll need to enable
#       coretemp kernel module. This only supports newer Intel processors.
#
# Example:
# C:   'Intel i7-6500U (4) @ 3.1GHz [27.2°C]'
# F:   'Intel i7-6500U (4) @ 3.1GHz [82.0°F]'
# off: 'Intel i7-6500U (4) @ 3.1GHz'
cpu_temp="off"

# GPU

# Enable/Disable GPU Brand
#
# Default: 'on'
# Values:  'on', 'off'
# Flag:    --gpu_brand
#
# Example:
# on:  'AMD HD 7950'
# off: 'HD 7950'
gpu_brand="on"

# Which GPU to display
#
# Default: 'all'
# Values:  'all', 'dedicated', 'integrated'
# Flag:    --gpu_type
# Supports: Linux
#
# Example:
# all:
#   GPU1: AMD HD 7950
#   GPU2: Intel Integrated Graphics
#
# dedicated:
#   GPU1: AMD HD 7950
#
# integrated:
#   GPU1: Intel Integrated Graphics
gpu_type="all"

# Resolution

# Display refresh rate next to each monitor
# Default: 'off'
# Values:  'on', 'off'
# Flag:    --refresh_rate
# Supports: Doesn't work on Windows.
#
# Example:
# on:  '1920x1080 @ 60Hz'
# off: '1920x1080'
refresh_rate="off"

# Gtk Theme / Icons / Font

# Shorten output of GTK Theme / Icons / Font
#
# Default: 'off'
# Values:  'on', 'off'
# Flag:    --gtk_shorthand
#
# Example:
# on:  'Numix, Adwaita'
# off: 'Numix [GTK2], Adwaita [GTK3]'
gtk_shorthand="off"

# Enable/Disable gtk2 Theme / Icons / Font
#
# Default: 'on'
# Values:  'on', 'off'
# Flag:    --gtk2
#
# Example:
# on:  'Numix [GTK2], Adwaita [GTK3]'
# off: 'Adwaita [GTK3]'
gtk2="on"

# Enable/Disable gtk3 Theme / Icons / Font
#
# Default: 'on'
# Values:  'on', 'off'
# Flag:    --gtk3
#
# Example:
# on:  'Numix [GTK2], Adwaita [GTK3]'
# off: 'Numix [GTK2]'
gtk3="on"

# IP Address

# Website to ping for the public IP
#
# Default: 'http://ident.me'
# Values:  'url'
# Flag:    --ip_host
public_ip_host="http://ident.me"

# Public IP timeout.
#
# Default: '2'
# Values:  'int'
# Flag:    --ip_timeout
public_ip_timeout=2

# Desktop Environment

# Show Desktop Environment version
#
# Default: 'on'
# Values:  'on', 'off'
# Flag:    --de_version
de_version="on"

# Disk

# Which disks to display.
# The values can be any /dev/sdXX, mount point or directory.
# NOTE: By default we only show the disk info for '/'.
#
# Default: '/'
# Values:  '/', '/dev/sdXX', '/path/to/drive'.
# Flag:    --disk_show
#
# Example:
# disk_show=('/' '/dev/sdb1'):
#      'Disk (/): 74G / 118G (66%)'
#      'Disk (/mnt/Videos): 823G / 893G (93%)'
#
# disk_show=('/'):
#      'Disk (/): 74G / 118G (66%)'
#
disk_show=('/')

# Disk subtitle.
# What to append to the Disk subtitle.
#
# Default: 'mount'
# Values:  'mount', 'name', 'dir', 'none'
# Flag:    --disk_subtitle
#
# Example:
# name:   'Disk (/dev/sda1): 74G / 118G (66%)'
#         'Disk (/dev/sdb2): 74G / 118G (66%)'
#
# mount:  'Disk (/): 74G / 118G (66%)'
#         'Disk (/mnt/Local Disk): 74G / 118G (66%)'
#         'Disk (/mnt/Videos): 74G / 118G (66%)'
#
# dir:    'Disk (/): 74G / 118G (66%)'
#         'Disk (Local Disk): 74G / 118G (66%)'
#         'Disk (Videos): 74G / 118G (66%)'
#
# none:   'Disk: 74G / 118G (66%)'
#         'Disk: 74G / 118G (66%)'
#         'Disk: 74G / 118G (66%)'
disk_subtitle="mount"

# Disk percent.
# Show/Hide disk percent.
#
# Default: 'on'
# Values:  'on', 'off'
# Flag:    --disk_percent
#
# Example:
# on:  'Disk (/): 74G / 118G (66%)'
# off: 'Disk (/): 74G / 118G'
disk_percent="on"

# Song

# Manually specify a music player.
#
# Default: 'auto'
# Values:  'auto', 'player-name'
# Flag:    --music_player
#
# Available values for 'player-name':
#
# amarok
# audacious
# banshee
# bluemindo
# clementine
# cmus
# deadbeef
# deepin-music
# dragon
# elisa
# exaile
# gnome-music
# gmusicbrowser
# gogglesmm
# guayadeque
# io.elementary.music
# iTunes
# juk
# lollypop
# mocp
# mopidy
# mpd
# muine
# netease-cloud-music
# olivia
# playerctl
# pogo
# pragha
# qmmp
# quodlibet
# rhythmbox
# sayonara
# smplayer
# spotify
# strawberry
# tauonmb
# tomahawk
# vlc
# xmms2d
# xnoise
# yarock
music_player="auto"

# Format to display song information.
#
# Default: '%artist% - %album% - %title%'
# Values:  '%artist%', '%album%', '%title%'
# Flag:    --song_format
#
# Example:
# default: 'Song: Jet - Get Born - Sgt Major'
song_format="%artist% - %album% - %title%"

# Print the Artist, Album and Title on separate lines
#
# Default: 'off'
# Values:  'on', 'off'
# Flag:    --song_shorthand
#
# Example:
# on:  'Artist: The Fratellis'
#      'Album: Costello Music'
#      'Song: Chelsea Dagger'
#
# off: 'Song: The Fratellis - Costello Music - Chelsea Dagger'
song_shorthand="off"

# 'mpc' arguments (specify a host, password etc).
#
# Default:  ''
# Example: mpc_args=(-h HOST -P PASSWORD)
mpc_args=()

# Text Colors

# Text Colors
#
# Default:  'distro'
# Values:   'distro', 'num' 'num' 'num' 'num' 'num' 'num'
# Flag:     --colors
#
# Each number represents a different part of the text in
# this order: 'title', '@', 'underline', 'subtitle', 'colon', 'info'
#
# Example:
# colors=(distro)      - Text is colored based on Distro colors.
# colors=(4 6 1 8 8 6) - Text is colored in the order above.
colors=(13 3 3 13 13 7)

# Text Options

# Toggle bold text
#
# Default:  'on'
# Values:   'on', 'off'
# Flag:     --bold
bold="on"

# Enable/Disable Underline
#
# Default:  'on'
# Values:   'on', 'off'
# Flag:     --underline
underline_enabled="on"

# Underline character
#
# Default:  '-'
# Values:   'string'
# Flag:     --underline_char
underline_char="-"

# Info Separator
# Replace the default separator with the specified string.
#
# Default:  ':'
# Flag:     --separator
#
# Example:
# separator="->":   'Shell-> bash'
# separator=" =":   'WM = dwm'
separator=":"

# Color Blocks

# Color block range
# The range of colors to print.
#
# Default:  '0', '15'
# Values:   'num'
# Flag:     --block_range
#
# Example:
#
# Display colors 0-7 in the blocks.  (8 colors)
# neofetch --block_range 0 31
#
# Display colors 0-15 in the blocks. (16 colors)
# neofetch --block_range 0 31
block_range=(0 31)

# Toggle color blocks
#
# Default:  'on'
# Values:   'on', 'off'
# Flag:     --color_blocks
color_blocks="on"

# Color block width in spaces
#
# Default:  '3'
# Values:   'num'
# Flag:     --block_width
block_width=3

# Color block height in lines
#
# Default:  '1'
# Values:   'num'
# Flag:     --block_height
block_height=1

# Color Alignment
#
# Default: 'auto'
# Values: 'auto', 'num'
# Flag: --col_offset
#
# Number specifies how far from the left side of the terminal (in spaces) to
# begin printing the columns, in case you want to e.g. center them under your
# text.
# Example:
# col_offset="auto" - Default behavior of neofetch
# col_offset=7      - Leave 7 spaces then print the colors
col_offset="auto"

# Progress Bars

# Bar characters
#
# Default:  '-', '='
# Values:   'string', 'string'
# Flag:     --bar_char
#
# Example:
# neofetch --bar_char 'elapsed' 'total'
# neofetch --bar_char '-' '='
bar_char_elapsed="-"
bar_char_total="="

# Toggle Bar border
#
# Default:  'on'
# Values:   'on', 'off'
# Flag:     --bar_border
bar_border="on"

# Progress bar length in spaces
# Number of chars long to make the progress bars.
#
# Default:  '15'
# Values:   'num'
# Flag:     --bar_length
bar_length=15

# Progress bar colors
# When set to distro, uses your distro's logo colors.
#
# Default:  'distro', 'distro'
# Values:   'distro', 'num'
# Flag:     --bar_colors
#
# Example:
# neofetch --bar_colors 3 4
# neofetch --bar_colors distro 5
bar_color_elapsed="distro"
bar_color_total="distro"

# Info display
# Display a bar with the info.
#
# Default: 'off'
# Values:  'bar', 'infobar', 'barinfo', 'off'
# Flags:   --cpu_display
#          --memory_display
#          --battery_display
#          --disk_display
#
# Example:
# bar:     '[---=======]'
# infobar: 'info [---=======]'
# barinfo: '[---=======] info'
# off:     'info'
cpu_display="off"
memory_display="off"
battery_display="off"
disk_display="off"

# Backend Settings

# Image backend.
#
# Default:  'ascii'
# Values:   'ascii', 'caca', 'chafa', 'jp2a', 'iterm2', 'off',
#           'pot', 'termpix', 'pixterm', 'tycat', 'w3m', 'kitty'
# Flag:     --backend
image_backend="ascii"

# Image Source
#
# Which image or ascii file to display.
#
# Default:  'auto'
# Values:   'auto', 'ascii', 'wallpaper', '/path/to/img', '/path/to/ascii', '/path/to/dir/'
#           'command output (neofetch --ascii "$(fortune | cowsay -W 30)")'
# Flag:     --source
#
# NOTE: 'auto' will pick the best image source for whatever image backend is used.
#       In ascii mode, distro ascii art will be used and in an image mode, your
#       wallpaper will be used.
image_source="/home/$USER/.config/neofetch/ascii"

# Ascii Options

# Ascii distro
# Which distro's ascii art to display.
#
# Default: 'auto'
# Values:  'auto', 'distro_name'
# Flag:    --ascii_distro
# NOTE: AIX, Alpine, Anarchy, Android, Antergos, antiX, "AOSC OS",
#       "AOSC OS/Retro", Apricity, ArcoLinux, ArchBox, ARCHlabs,
#       ArchStrike, XFerience, ArchMerge, Arch, Artix, Arya, Bedrock,
#       Bitrig, BlackArch, BLAG, BlankOn, BlueLight, bonsai, BSD,
#       BunsenLabs, Calculate, Carbs, CentOS, Chakra, ChaletOS,
#       Chapeau, Chrom*, Cleanjaro, ClearOS, Clear_Linux, Clover,
#       Condres, Container_Linux, CRUX, Cucumber, Debian, Deepin,
#       DesaOS, Devuan, DracOS, DarkOs, DragonFly, Drauger, Elementary,
#       EndeavourOS, Endless, EuroLinux, Exherbo, Fedora, Feren, FreeBSD,
#       FreeMiNT, Frugalware, Funtoo, GalliumOS, Garuda, Gentoo, Pentoo,
#       gNewSense, GNOME, GNU, GoboLinux, Grombyang, Guix, Haiku, Huayra,
#       Hyperbola, janus, Kali, KaOS, KDE_neon, Kibojoe, Kogaion,
#       Korora, KSLinux, Kubuntu, LEDE, LFS, Linux_Lite,
#       LMDE, Lubuntu, Lunar, macos, Mageia, MagpieOS, Mandriva,
#       Manjaro, Maui, Mer, Minix, LinuxMint, MX_Linux, Namib,
#       Neptune, NetBSD, Netrunner, Nitrux, NixOS, Nurunner,
#       NuTyX, OBRevenge, OpenBSD, openEuler, OpenIndiana, openmamba,
#       OpenMandriva, OpenStage, OpenWrt, osmc, Oracle, OS Elbrus, PacBSD,
#       Parabola, Pardus, Parrot, Parsix, TrueOS, PCLinuxOS, Peppermint,
#       popos, Porteus, PostMarketOS, Proxmox, Puppy, PureOS, Qubes, Radix,
#       Raspbian, Reborn_OS, Redstar, Redcore, Redhat, Refracted_Devuan,
#       Regata, Rosa, sabotage, Sabayon, Sailfish, SalentOS, Scientific,
#       Septor, SereneLinux, SharkLinux, Siduction, Slackware, SliTaz,
#       SmartOS, Solus, Source_Mage, Sparky, Star, SteamOS, SunOS,
#       openSUSE_Leap, openSUSE_Tumbleweed, openSUSE, SwagArch, Tails,
#       Trisquel, Ubuntu-Budgie, Ubuntu-GNOME, Ubuntu-MATE, Ubuntu-Studio,
#       Ubuntu, Venom, Void, Obarun, windows10, Windows7, Xubuntu, Zorin,
#       and IRIX have ascii logos
# NOTE: Arch, Ubuntu, Redhat, and Dragonfly have 'old' logo variants.
#       Use '{distro name}_old' to use the old logos.
# NOTE: Ubuntu has flavor variants.
#       Change this to Lubuntu, Kubuntu, Xubuntu, Ubuntu-GNOME,
#       Ubuntu-Studio, Ubuntu-Mate  or Ubuntu-Budgie to use the flavors.
# NOTE: Arcolinux, Dragonfly, Fedora, Alpine, Arch, Ubuntu,
#       CRUX, Debian, Gentoo, FreeBSD, Mac, NixOS, OpenBSD, android,
#       Antrix, CentOS, Cleanjaro, ElementaryOS, GUIX, Hyperbola,
#       Manjaro, MXLinux, NetBSD, Parabola, POP_OS, PureOS,
#       Slackware, SunOS, LinuxLite, OpenSUSE, Raspbian,
#       postmarketOS, and Void have a smaller logo variant.
#       Use '{distro name}_small' to use the small variants.
ascii_distro="auto"

# Ascii Colors
#
# Default:  'distro'
# Values:   'distro', 'num' 'num' 'num' 'num' 'num' 'num'
# Flag:     --ascii_colors
#
# Example:
# ascii_colors=(distro)      - Ascii is colored based on Distro colors.
# ascii_colors=(4 6 1 8 8 6) - Ascii is colored using these colors.
ascii_colors=(distro)

# Bold ascii logo
# Whether or not to bold the ascii logo.
#
# Default: 'on'
# Values:  'on', 'off'
# Flag:    --ascii_bold
ascii_bold="on"

# Image Options

# Image loop
# Setting this to on will make neofetch redraw the image constantly until
# Ctrl+C is pressed. This fixes display issues in some terminal emulators.
#
# Default:  'off'
# Values:   'on', 'off'
# Flag:     --loop
image_loop="off"

# Thumbnail directory
#
# Default: '~/.cache/thumbnails/neofetch'
# Values:  'dir'
thumbnail_dir="${XDG_CACHE_HOME:-${HOME}/.cache}/thumbnails/neofetch"

# Crop mode
#
# Default:  'normal'
# Values:   'normal', 'fit', 'fill'
# Flag:     --crop_mode
#
# See this wiki page to learn about the fit and fill options.
# https://github.com/dylanaraps/neofetch/wiki/What-is-Waifu-Crop%3F
crop_mode="normal"

# Crop offset
# Note: Only affects 'normal' crop mode.
#
# Default:  'center'
# Values:   'northwest', 'north', 'northeast', 'west', 'center'
#           'east', 'southwest', 'south', 'southeast'
# Flag:     --crop_offset
crop_offset="center"

# Image size
# The image is half the terminal width by default.
#
# Default: 'auto'
# Values:  'auto', '00px', '00%', 'none'
# Flags:   --image_size
#          --size
image_size="auto"

# Gap between image and text
#
# Default: '3'
# Values:  'num', '-num'
# Flag:    --gap
gap=3

# Image offsets
# Only works with the w3m backend.
#
# Default: '0'
# Values:  'px'
# Flags:   --xoffset
#          --yoffset
yoffset=0
xoffset=0

# Image background color
# Only works with the w3m backend.
#
# Default: ''
# Values:  'color', 'blue'
# Flag:    --bg_color
background_color=

# Misc Options

# Stdout mode
# Turn off all colors and disables image backend (ASCII/Image).
# Useful for piping into another command.
# Default: 'off'
# Values: 'on', 'off'
stdout="off"


text:
version: "3"
services:
 vita:
      build: .
      image: vita
      container_name: vita
      restart: always

repo_name: junnlikestea/vita

repo_language: Rust


text:
[package]
name = "vita"
version = "0.1.14"
authors = ["Junn <likesgophers@gmail.com>"]
description = "vita is a tool to gather subdomains from passive sources"
categories = ["command-line-utilities", "passive reconnaissance"]
keywords = ["recon", "passive", "subdomain enumeration"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
bench = false
path = "src/bin/main.rs"
name = "vita"

[dependencies]
reqwest = {version = "0.10.7", features = ["json", "gzip"]}
strum = "0.20"
strum_macros = "0.20"
matches = "0.1.8"
async-trait = "0.1.41"
crobat = {path = "./crobat" }
base64 = "0.12.3"
addr = "0.2.0"
clap = "2.33.1"
url = "2.1.1"
futures = "0.3.5"
futures-core = "0.3.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
dotenv = "0.15.0"
tokio = { version = "0.2", features = ["sync", "rt-threaded", "macros"] }
tracing = {version = "0.1.19", features = ["attributes"]}
tracing-futures = "0.2.4"
tracing-subscriber = "0.2.12"

[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
openssl = { version = "0.10", features = ["vendored"] }


text:
FROM alpine:latest

WORKDIR vita

RUN apk add \
        wget\
        tar\
        gzip\
        curl\
        jq

RUN export LATEST_RELEASE=$(curl -s https://api.github.com/repos/junnlikestea/vita/releases/latest | jq -jr .tag_name) &&\
                wget https://github.com/junnlikestea/vita/releases/download/$LATEST_RELEASE/vita-$LATEST_RELEASE-x86_64-unknown-linux-musl.tar.gz &&\
                tar -xzvf vita-$LATEST_RELEASE-x86_64-unknown-linux-musl.tar.gz && \
                mv vita-$LATEST_RELEASE-x86_64-unknown-linux-musl/vita . &&\
                rm -rf vita-*

RUN apk del --purge\
        wget\
        tar\
        gzip\
        jq\
        curl
ENV HOME /

CMD ["./vita -d"]
ENTRYPOINT ["/vita/vita"]
### Debug Module ###

#CMD ["tail", "-f", "/dev/null"]


text:
# Vita
![release](https://github.com/junnlikestea/vita/workflows/release/badge.svg)
[![Build status](https://github.com/junnlikestea/vita/workflows/Continuous%20integration/badge.svg)](https://github.com/junnlikestea/vita/actions)

Vita is a tool to gather subdomains from passive sources much like [tomnomnom's assetfinder](https://github.com/tomnomnom/assetfinder).

### Installation
Precompiled binaries for vita are available in the [releases](https://github.com/junnlikestea/vita/releases) tab. Just pick your platform and extract the archive that contains the binary.

### Building it yourself
If you want to build it yourself you will need to install Rust, you can get the [official installation](https://www.rust-lang.org/tools/install) from the Rust website.

To build Vita:
```
$ git clone https://github.com/junnlikestea/vita
$ cd vita
$ cargo build --release
$ ./target/release/vita --version
```

### Usage

**With a single domain and collecting data from free sources**

```
vita -d hackerone.com
```
By default the results will be unique, and will filter subdomains not related
to your root domain, or domains if you choose to supply multiple.

**Collecting data only on a specific subdomain**

If you only want to collect results related to a specific subdomain you can use
the `--subs-only` flag. This will cause vita to run on the actual domain and not
the root domain. Results will be filtered to anything that ends with the input
domain or domains.
```
vita -d api.hackerone.com --subs-only
```

**With a list of domains from a file**

```
vita -f path/to/domains.txt
```

**With a list of domains from stdin**

```
vita < /path/to/domains.txt
```

**Outputting results as they're received**

You can output results as they arrive as opposed to once all sources finish using
the `--flush` flag. You might want to use this flag when you're running vita on a
small vps without much memory. When this flag is active vita will not remove duplicates,
so you may want to pipe it through something like `sort -u`.
```
vita -d hackerone.com --flush
```

**Collecting data using paid sources**

If you want to include sources which require API keys, add the `-a` or `-all` flag, for example:
```
vita -d hackerone.com -a
```
By default it will just ignore services you don't supply keys for.

**Excluding sources**

You can exclude sources with the `-e` flag
```
vita -d hackerone.com -e Wayback
```

If you would like some more verbose output you can use the `-v` flag. There are
different levels of verbosity ranging from noisy to informational, most of the
time I just use `info`. This is all printing to stderr, so it won't be captured
in the results.
* `info`: General information like how many results each source returned.
* `debug`: Lots and lots of information about what's going on under the hood.
```
vita -d hackerone.com -v info
```

### Common error - Too many open files
Vita uses async concurrent http requests under the hood. If you encounter an error
similar to *"Too many open files"* it means that there isn't enough available file descriptors on
your system. You can fix this by increasing the hard and soft limits. There are
lots of different guides available to increase the limits [but here is one for linux](https://www.tecmint.com/increase-set-open-file-limits-in-linux/).

### Sources
* SonarSearch
* C99
* ProjectDiscovery Chaos
* AnubisDB
* Alienvault
* Binaryedge
* Certspotter
* Crt.sh
* Hackertarget
* Threatcrowd
* VirusTotal
* Sublis3r
* Security Trails
* Spyse
* Urlscan.io
* Facebook
* Threatminer
* Wayback Machine
* IntelligenceX
* PassiveTotal

### How to set your Api Keys
Add a `.env` file to the tool directory or add the following to your existing `.env` file:
* Binaryedge:
	* Needs `BINARYEDGE_TOKEN` set
* ProjectDiscovery Chaos
	* Needs `CHAOS_KEY` set
* Facebook:
	* Needs `FB_APP_ID` and `FB_APP_SECRET` set.
* Spyse:
	* Needs `SPYSE_TOKEN` set.
* Security Trails:
	* Needs `SECURITY_TRAILS_KEY` set.
* C99:
	* Needs `C99_KEY` set.
* PassiveTotal:
	* Needs `PASSIVETOTAL_KEY` and `PASSIVETOTAL_SECRET` set
	* Can be found under the account settings page.
* IntelligenceX:
	* Needs `INTELX_KEY` and `INTELX_URL` to be set
	* Can be found under the [developer tab](https://intelx.io/account?tab=developer)

If you hit rate limits or authentication fails, the source will just be ignored from the list of potential sources.

### A note on tuning the concurrency
Currently Vita will limit the search for data to 200 root domains concurrently. If you would like to
change that limit you can use the `-c` flag:

```
vita -f /path/to/roots.txt -c 400
```

### Thanks
[0xatul](https://twitter.com/0xatul) For constant feedback and improvement ideas.

[dee-see](https://github.com/dee-see) For the contributions and neat issues!

[TomNomNom](https://twitter.com/TomNomNom) For inspiring me to write and release open source tools.

[Cgboal](https://twitter.com/CalumBoal) For [SonarSearch](https://github.com/Cgboal/SonarSearch)
which is a data source for Vita.

[ProjectDiscovery](https://chaos.projectdiscovery.io/#/) For Chaos which is a great data source.

Thanks to all the data source providers, and everyone else I can't seem to remember at this point
in time. I'll make sure to add you in the future.

### To-do
* Add more paid sources.
* Write some documentation for the underlying library that Vita uses, and prepare publish to crates.io.
* Clean up types and optimise performance further.

### Disclaimer
Developers have/has no responsibility or authority over any kind of:
* Legal or Law infringement by third parties and users.
* Malicious use capable of causing damage to third parties.
* Illegal or unlawful use of vita.


text:
# THIS FILE WAS TAKEN FROM  - https://github.com/BurntSushi/ripgrep
# All credit goes to BurntSushi!

name: release
on:
  push:
    # Enable when testing release infrastructure on a branch.
    # branches:
    # - ag/release
    tags:
    - '[0-9]+.[0-9]+.[0-9]+'
jobs:
  create-release:
    name: create-release
    runs-on: ubuntu-latest
    # env:
      # Set to force version number, e.g., when no tag exists.
      # VITA_VERSION: TEST-0.0.0
    steps:
      - name: Create artifacts directory
        run: mkdir artifacts

      - name: Get the release version from the tag
        env:
          ACTIONS_ALLOW_UNSECURE_COMMANDS: "true"
        if: env.VITA_VERSION == ''
        run: |
          # Apparently, this is the right way to get a tag name. Really?
          #
          # See: https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
          echo "::set-env name=VITA_VERSION::${GITHUB_REF#refs/tags/}"
          echo "version is: ${{ env.VITA_VERSION }}"

      - name: Create GitHub release
        id: release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ env.VITA_VERSION }}
          release_name: ${{ env.VITA_VERSION }}

      - name: Save release upload URL to artifact
        run: echo "${{ steps.release.outputs.upload_url }}" > artifacts/release-upload-url

      - name: Save version number to artifact
        run: echo "${{ env.VITA_VERSION }}" > artifacts/release-version

      - name: Upload artifacts
        uses: actions/upload-artifact@v1
        with:
          name: artifacts
          path: artifacts

  build-release:
    name: build-release
    needs: ['create-release']
    runs-on: ${{ matrix.os }}
    env:
      # For some builds, we use cross to test on 32-bit and big-endian
      # systems.
      CARGO: cargo
      # When CARGO is set to CROSS, this is set to `--target matrix.target`.
      TARGET_FLAGS:
      # When CARGO is set to CROSS, TARGET_DIR includes matrix.target.
      TARGET_DIR: ./target
      # Emit backtraces on panics.
      RUST_BACKTRACE: 1
      # Build static releases with PCRE2.
      PCRE2_SYS_STATIC: 1
    strategy:
      matrix:
        build: [linux, macos, win-msvc, win-gnu, win32-msvc]
        include:
        - build: linux
          os: ubuntu-18.04
          rust: stable
          target: x86_64-unknown-linux-musl
        - build: macos
          os: macos-latest
          rust: stable
          target: x86_64-apple-darwin
        - build: win-msvc
          os: windows-2019
          rust: stable
          target: x86_64-pc-windows-msvc
        - build: win-gnu
          os: windows-2019
          rust: stable-x86_64-gnu
          target: x86_64-pc-windows-gnu
        - build: win32-msvc
          os: windows-2019
          rust: stable
          target: i686-pc-windows-msvc

    steps:
    - name: Checkout repository
      uses: actions/checkout@v2
      with:
        fetch-depth: 1

    - name: Install Rust
      uses: actions-rs/toolchain@v1
      with:
        toolchain: ${{ matrix.rust }}
        profile: minimal
        override: true
        target: ${{ matrix.target }}
        components: rustfmt

    - name: Use Cross
      env:
        ACTIONS_ALLOW_UNSECURE_COMMANDS: "true"
      # if: matrix.os != 'windows-2019'
      run: |
        # FIXME: to work around bugs in latest cross release, install master.
        # See: https://github.com/rust-embedded/cross/issues/357
        cargo install --git https://github.com/rust-embedded/cross
        echo "::set-env name=CARGO::cross"
        echo "::set-env name=TARGET_FLAGS::--target ${{ matrix.target }}"
        echo "::set-env name=TARGET_DIR::./target/${{ matrix.target }}"

    - name: Show command used for Cargo
      run: |
        echo "cargo command is: ${{ env.CARGO }}"
        echo "target flag is: ${{ env.TARGET_FLAGS }}"
        echo "target dir is: ${{ env.TARGET_DIR }}"

    - name: Get release download URL
      uses: actions/download-artifact@v1
      with:
        name: artifacts
        path: artifacts

    - name: Set release upload URL and release version
      env:
        ACTIONS_ALLOW_UNSECURE_COMMANDS: "true"
      shell: bash
      run: |
        release_upload_url="$(cat artifacts/release-upload-url)"
        echo "::set-env name=RELEASE_UPLOAD_URL::$release_upload_url"
        echo "release upload url: $RELEASE_UPLOAD_URL"
        release_version="$(cat artifacts/release-version)"
        echo "::set-env name=RELEASE_VERSION::$release_version"
        echo "release version: $RELEASE_VERSION"

    - name: Build release binary
      run: ${{ env.CARGO }} build --verbose --release ${{ env.TARGET_FLAGS }}

    - name: Strip release binary (linux and macos)
      if: matrix.build == 'linux' || matrix.build == 'macos'
      run: strip "target/${{ matrix.target }}/release/vita"

    - name: Build archive
      env:
        ACTIONS_ALLOW_UNSECURE_COMMANDS: "true"
      shell: bash
      run: |
        staging="vita-${{ env.RELEASE_VERSION }}-${{ matrix.target }}"
        mkdir -p "$staging"/

        if [ "${{ matrix.os }}" = "windows-2019" ]; then
          cp "target/${{ matrix.target }}/release/vita.exe" "$staging/"
          7z a "$staging.zip" "$staging"
          echo "::set-env name=ASSET::$staging.zip"
        else
          cp "target/${{ matrix.target }}/release/vita" "$staging/"
          tar czf "$staging.tar.gz" "$staging"
          echo "::set-env name=ASSET::$staging.tar.gz"
        fi

    - name: Upload release archive
      uses: actions/upload-release-asset@v1.0.1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ env.RELEASE_UPLOAD_URL }}
        asset_path: ${{ env.ASSET }}
        asset_name: ${{ env.ASSET }}
        asset_content_type: application/octet-stream


text:
on: [push, pull_request]

name: Continuous integration

jobs:
  linux:
    name: Linux
    runs-on: ubuntu-latest
    strategy:
      matrix:
        toolchain:
          - stable
        arch:
          - i686-unknown-linux-gnu
          - x86_64-unknown-linux-gnu
    steps:
      - uses: actions/checkout@master
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: ${{ matrix.toolchain }}
          target: ${{ matrix.arch }}
          override: true

      - name: Build
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: build
          args: --target ${{ matrix.arch }}

      - name: Run tests
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: test
          args: --target ${{ matrix.arch }}

      - name: Run clippy
        if: matrix.toolchain == 'stable'
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: clippy
          args: --target ${{ matrix.arch }} -- -D warnings

  windows:
    name: Windows
    runs-on: windows-latest
    strategy:
      matrix:
        toolchain:
          - stable
        arch:
          - i686-pc-windows-msvc
          - x86_64-pc-windows-msvc
    steps:
      - uses: actions/checkout@master
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: ${{ matrix.toolchain }}
          target: ${{ matrix.arch }}
          override: true

      - name: Build
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: build
          args: --target ${{ matrix.arch }}

      - name: Run tests
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: test
          args: --target ${{ matrix.arch }}

      - name: Run clippy
        if: matrix.toolchain == 'stable'
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: clippy
          args: --target ${{ matrix.arch }} -- -D warnings

  macos:
    name: macOS
    runs-on: macOS-latest
    strategy:
      matrix:
        toolchain:
          - stable
        arch:
          - x86_64-apple-darwin
    steps:
      - uses: actions/checkout@master
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: ${{ matrix.toolchain }}
          target: ${{ matrix.arch }}
          override: true
          components: rustfmt

      - name: Build
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: build
          args: --target ${{ matrix.arch }}

      - name: Run tests
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: test
          args: --target ${{ matrix.arch }}

      - name: Run clippy
        if: matrix.toolchain == 'stable'
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: clippy
          args: --target ${{ matrix.arch }} -- -D warnings


text:
use addr::DomainName;
use std::collections::HashSet;
use std::hash::Hash;

/// Represents the filtering applied to the output
enum Filter {
    /// Return any result that matches the same subdomain
    SubOnly,
    /// Return any result that has the same root domain
    RootOnly,
}

impl Default for Filter {
    fn default() -> Self {
        Self::RootOnly
    }
}

/// `PostProcessor` is responsible for filtering the raw data from each of the data sources into
/// only those results which are relevant.
#[derive(Default)]
pub struct PostProcessor {
    roots: HashSet<String>,
    filter: Filter,
}

impl PostProcessor {
    /// Sets the `PostProcessor` to return any result which matches the same root domain
    pub fn any_root<I: IntoIterator<Item = String>>(&mut self, hosts: I) -> &mut Self {
        self.roots = hosts
            .into_iter()
            .filter_map(|d| d.parse::<DomainName>().ok())
            .map(|d| d.root().to_string())
            .collect();
        self.filter = Filter::RootOnly;
        self
    }

    /// Sets the `PostProcessor` to return any result which matches the same subdomain
    pub fn any_subdomain<I: IntoIterator<Item = String>>(&mut self, hosts: I) -> &mut Self {
        self.roots.extend(hosts);
        self.filter = Filter::SubOnly;
        self
    }

    /// Strips invalid characters from the domain
    ///
    /// Used before attempting to parse a domain into  a `add::DomainName`.
    ///
    /// Errors
    /// If the the input domain contains any invalid characters the
    /// attempting to parse it into a `addr::DomainName` would return an error
    fn strip_invalid<T: AsRef<str> + std::fmt::Display>(domain: T) -> String {
        domain
            .as_ref()
            .strip_prefix('.')
            .unwrap_or_else(|| domain.as_ref())
            .replace(&['"', '\"', '\\', '*'][..], "")
            .to_lowercase()
    }

    /// Determines if the domain is a result we're interested in
    ///
    /// By default, a "relevant" result is any result that has the same root domain
    /// as one of the input domains. For example, if you provided `example.com` as an
    /// input domain `some.sub.example.com` would be a relevant result using default
    /// config. The non-default `Filter::SubOnly` will apply no filtering to the input
    /// domains and a relevant result will be any that has the same suffix as one of the
    /// input domains.
    fn is_relevant<T: AsRef<str>>(&self, result: T) -> bool {
        match self.filter {
            Filter::RootOnly => {
                if let Ok(d) = result.as_ref().parse::<DomainName>() {
                    self.roots.contains(d.root().to_str())
                } else {
                    false
                }
            }
            Filter::SubOnly => self
                .roots
                .iter()
                .any(|root| result.as_ref().ends_with(root) && !result.as_ref().eq(root)),
        }
    }
}

/// An iterator which uses `PostProcessor` to filter each `Iterator::Item`
pub struct PostProcessorIter<'a, I>
where
    I: Iterator,
{
    cleaner: &'a PostProcessor,
    inner: I,
}

impl<'a, I> Iterator for PostProcessorIter<'a, I>
where
    I: Iterator,
    I::Item: Hash + Eq + AsRef<str>,
{
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        while let Some(d) = self.inner.next() {
            let cleaned = PostProcessor::strip_invalid(d.as_ref());
            if self.cleaner.is_relevant(&cleaned) {
                return Some(cleaned);
            }
        }
        None
    }
}

pub trait CleanExt: Iterator {
    fn clean(self, postprocessor: &PostProcessor) -> PostProcessorIter<Self>
    where
        Self::Item: Hash + Eq + AsRef<str>,
        Self: Sized,
    {
        PostProcessorIter {
            cleaner: postprocessor,
            inner: self,
        }
    }
}

impl<I: Iterator> CleanExt for I {}


text:
use std::error::Error;
use std::fmt::{self, Formatter};

pub type Result<T> = std::result::Result<T, VitaError>;

#[derive(Debug)]
pub enum VitaError {
    SourceError(String),
    AuthError(String),
    UnsetKeys(Vec<String>),
    ReqwestError(reqwest::Error),
    JoinError(tokio::task::JoinError),
    IoError(std::io::Error),
    Msg(String),
    ParseError,
    CrobatError,
    EmptyResults,
}

impl fmt::Display for VitaError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            VitaError::SourceError(s) => write!(f, "couldn't fetch data from {}", s),
            VitaError::AuthError(s) => {
                write!(
                    f,
                    "error authenticating to {} or may have hit rate limits",
                    s
                )
            }
            VitaError::UnsetKeys(v) => write!(f, "error reading environment variables {:?}", v),
            VitaError::EmptyResults => write!(f, "returned no results"),
            VitaError::CrobatError => {
                write!(f, "got error when trying to pull results from crobat")
            }
            VitaError::ParseError => write!(f, "got error trying to parse cli args"),
            VitaError::Msg(s) => write!(f, "got error {}", s),
            VitaError::ReqwestError(ref err) => err.fmt(f),
            VitaError::JoinError(ref err) => err.fmt(f),
            VitaError::IoError(ref err) => err.fmt(f),
        }
    }
}

impl Error for VitaError {}

impl From<String> for VitaError {
    fn from(err: String) -> Self {
        VitaError::Msg(err)
    }
}

impl From<reqwest::Error> for VitaError {
    fn from(err: reqwest::Error) -> Self {
        VitaError::ReqwestError(err)
    }
}

impl From<tokio::task::JoinError> for VitaError {
    fn from(err: tokio::task::JoinError) -> Self {
        VitaError::JoinError(err)
    }
}

impl From<std::io::Error> for VitaError {
    fn from(err: std::io::Error) -> Self {
        VitaError::IoError(err)
    }
}

impl From<std::num::ParseIntError> for VitaError {
    fn from(_: std::num::ParseIntError) -> Self {
        VitaError::ParseError
    }
}

// Monkey patch until I add custom error type to Crobat
impl From<Box<dyn Error + Sync + std::marker::Send>> for VitaError {
    fn from(_: Box<dyn Error + Sync + std::marker::Send>) -> Self {
        VitaError::CrobatError
    }
}


text:
pub mod alienvault;
pub mod anubisdb;
pub mod binaryedge;
pub mod c99;
pub mod certspotter;
pub mod chaos;
pub mod crtsh;
pub mod facebook;
pub mod hackertarget;
pub mod intelx;
pub mod passivetotal;
pub mod securitytrails;
pub mod sonarsearch;
pub mod spyse;
pub mod sublister;
pub mod threatcrowd;
pub mod threatminer;
pub mod urlscan;
pub mod virustotal;
pub mod wayback;


text:
use crate::sources::{
    alienvault::AlienVault, anubisdb::AnubisDB, binaryedge::BinaryEdge, c99::C99,
    certspotter::CertSpotter, chaos::Chaos, crtsh::Crtsh, facebook::Facebook,
    hackertarget::HackerTarget, intelx::Intelx, passivetotal::PassiveTotal,
    securitytrails::SecurityTrails, sonarsearch::SonarSearch, spyse::Spyse, sublister::Sublister,
    threatcrowd::ThreatCrowd, threatminer::ThreatMiner, urlscan::UrlScan, virustotal::VirusTotal,
    wayback::Wayback,
};
use crate::{client, error::Result, DataSource};

use futures::stream::{FuturesUnordered, StreamExt};
use futures_core::stream::Stream;
use reqwest::Client;
use std::collections::HashMap;
use std::collections::HashSet;
use std::str::FromStr;
use std::sync::Arc;
use strum_macros::EnumString;
use tokio::sync::mpsc;
use tracing::{info, warn};

const CHAN_SIZE: usize = 255;

#[derive(Debug, Eq, PartialEq, Hash, EnumString)]
enum Source {
    AlienVault,
    AnubisDB,
    BinaryEdge,
    C99,
    CertSpotter,
    Chaos,
    Crtsh,
    Facebook,
    HackerTarget,
    Intelx,
    PassiveTotal,
    SecurityTrails,
    SonarSearch,
    Spyse,
    Sublister,
    ThreatCrowd,
    ThreatMiner,
    UrlScan,
    VirusTotal,
    Wayback,
}

// Configuration options for the `Runner`
struct Config {
    timeout: u64,
    // The maximum number of conurrent tasks
    concurrency: usize,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            timeout: 15,
            concurrency: 200,
        }
    }
}

// The `Runner` is responsible for collecting data from all the sources.
pub struct Runner {
    client: Client,
    sources: HashMap<Source, Arc<dyn DataSource>>,
    config: Config,
}

impl Default for Runner {
    fn default() -> Self {
        let config = Config::default();
        Self {
            client: client!(config.timeout, config.timeout),
            sources: HashMap::new(),
            config,
        }
    }
}

impl Runner {
    /// Sets the limit of concurrent tasks
    pub fn concurrency(mut self, limit: usize) -> Self {
        self.config.concurrency = limit;
        self
    }

    /// Sets the request timeout
    pub fn timeout(mut self, duration: u64) -> Self {
        self.config.timeout = duration;
        self
    }

    /// Excludes a collection sources from data collection
    pub fn exclude(mut self, excluded: &[&str]) -> Self {
        if !excluded.is_empty() {
            excluded.iter().for_each(|s| {
                if let Ok(source) = Source::from_str(s) {
                    info!("excluding {:?}", source);
                    self.sources.remove(&source);
                };
            });
        }

        self
    }

    /// Sets the sources to be all those which do not require an api key to use.
    pub fn free_sources(mut self) -> Self {
        // Client uses Arc internally
        let free: Vec<(Source, Arc<dyn DataSource>)> = vec![
            (
                Source::AnubisDB,
                Arc::new(AnubisDB::new(self.client.clone())),
            ),
            (
                Source::AlienVault,
                Arc::new(AlienVault::new(self.client.clone())),
            ),
            (
                Source::CertSpotter,
                Arc::new(CertSpotter::new(self.client.clone())),
            ),
            (
                Source::ThreatCrowd,
                Arc::new(ThreatCrowd::new(self.client.clone())),
            ),
            (
                Source::VirusTotal,
                Arc::new(VirusTotal::new(self.client.clone())),
            ),
            (
                Source::ThreatMiner,
                Arc::new(ThreatMiner::new(self.client.clone())),
            ),
            (
                Source::Sublister,
                Arc::new(Sublister::new(self.client.clone())),
            ),
            (
                Source::HackerTarget,
                Arc::new(HackerTarget::new(self.client.clone())),
            ),
            (
                Source::SonarSearch,
                Arc::new(SonarSearch::new(self.client.clone())),
            ),
            (Source::Wayback, Arc::new(Wayback::new(self.client.clone()))),
            (Source::UrlScan, Arc::new(UrlScan::new(self.client.clone()))),
            (Source::Crtsh, Arc::new(Crtsh::new(self.client.clone()))),
        ];

        self.sources.extend(free.into_iter());
        self
    }

    /// Sets the sources to include api keys in addition to the free sources.
    pub fn all_sources(mut self) -> Self {
        let all: Vec<(Source, Arc<dyn DataSource>)> = vec![
            (
                Source::AnubisDB,
                Arc::new(AnubisDB::new(self.client.clone())),
            ),
            (
                Source::AlienVault,
                Arc::new(AlienVault::new(self.client.clone())),
            ),
            (
                Source::CertSpotter,
                Arc::new(CertSpotter::new(self.client.clone())),
            ),
            (
                Source::ThreatCrowd,
                Arc::new(ThreatCrowd::new(self.client.clone())),
            ),
            (
                Source::VirusTotal,
                Arc::new(VirusTotal::new(self.client.clone())),
            ),
            (
                Source::ThreatMiner,
                Arc::new(ThreatMiner::new(self.client.clone())),
            ),
            (
                Source::Sublister,
                Arc::new(Sublister::new(self.client.clone())),
            ),
            (
                Source::SecurityTrails,
                Arc::new(SecurityTrails::new(self.client.clone())),
            ),
            (
                Source::HackerTarget,
                Arc::new(HackerTarget::new(self.client.clone())),
            ),
            (
                Source::SonarSearch,
                Arc::new(SonarSearch::new(self.client.clone())),
            ),
            (
                Source::BinaryEdge,
                Arc::new(BinaryEdge::new(self.client.clone())),
            ),
            (
                Source::PassiveTotal,
                Arc::new(PassiveTotal::new(self.client.clone())),
            ),
            (
                Source::Facebook,
                Arc::new(Facebook::new(self.client.clone())),
            ),
            (Source::Spyse, Arc::new(Spyse::new(self.client.clone()))),
            (Source::C99, Arc::new(C99::new(self.client.clone()))),
            (Source::Intelx, Arc::new(Intelx::new(self.client.clone()))),
            (Source::Wayback, Arc::new(Wayback::new(self.client.clone()))),
            (Source::UrlScan, Arc::new(UrlScan::new(self.client.clone()))),
            (Source::Crtsh, Arc::new(Crtsh::new(self.client.clone()))),
            (Source::Chaos, Arc::new(Chaos::new(self.client.clone()))),
        ];

        self.sources.extend(all.into_iter());
        self
    }

    /// Fetches data from the sources concurrently
    pub async fn run(self, hosts: HashSet<String>) -> Result<impl Stream<Item = Vec<String>>> {
        let (tx, rx) = mpsc::channel::<Vec<String>>(CHAN_SIZE);
        let sources = Arc::new(self.sources);
        let max_concurrent = self.config.concurrency;

        let tx2 = tx.clone();
        tokio::spawn(async move {
            let mut futures = FuturesUnordered::new();
            for host in hosts.into_iter() {
                let host = Arc::new(host);

                if futures.len() >= max_concurrent {
                    futures.next().await;
                }

                for source in sources.values() {
                    let source = Arc::clone(source);
                    let host = Arc::clone(&host);
                    let tx = tx2.clone();
                    futures.push(tokio::spawn(async move { source.run(host, tx).await }));
                }
            }

            // Get the remaining futures
            while let Some(res) = futures.next().await {
                if let Err(e) = res {
                    warn!("got error {} when trying to recv remaining futures", e)
                }
            }
        });

        // explicitly drop the remaning sender
        drop(tx);
        Ok(rx)
    }
}


text:
#![allow(clippy::rc_buffer)]

pub use crate::vita::Runner;
use async_trait::async_trait;
use error::Result;
pub use postprocessor::{CleanExt, PostProcessor, PostProcessorIter};
use std::sync::Arc;
use tokio::sync::mpsc;

pub mod error;
pub mod postprocessor;
pub mod sources;
pub mod vita;

// Arbitrary number for the queue capacity
pub(crate) const QUEUE_SIZE: usize = 1024;

trait IntoSubdomain {
    fn subdomains(&self) -> Vec<String>;
}

#[async_trait]
trait DataSource: Send + Sync {
    async fn run(&self, host: Arc<String>, mut tx: mpsc::Sender<Vec<String>>) -> Result<()>;
}

#[macro_export]
//https://stackoverflow.com/questions/24047686/default-function-arguments-in-rust
macro_rules! client {
    ($timeout:expr, $ptimeout:expr) => {
        reqwest::ClientBuilder::new()
            .timeout(std::time::Duration::from_secs($timeout))
            .pool_idle_timeout(std::time::Duration::from_secs($ptimeout))
            .build()
            .unwrap()
    };
    () => {
        reqwest::ClientBuilder::new()
            .timeout(std::time::Duration::from_secs(20))
            .pool_idle_timeout(std::time::Duration::from_secs(20))
            .build()
            .unwrap()
    };
}


text:
use crate::error::{Result, VitaError};
use crate::{DataSource, IntoSubdomain};
use async_trait::async_trait;
use dotenv::dotenv;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::env;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
use tracing::{debug, info, trace, warn};

struct Creds {
    url: String,
    api_key: String,
}

impl Creds {
    fn read_creds() -> Result<Self> {
        dotenv().ok();
        let api_key = env::var("INTELX_KEY");
        let url = env::var("INTELX_URL");

        match (api_key, url) {
            (Ok(k), Ok(u)) => Ok(Self { url: u, api_key: k }),
            _ => Err(VitaError::UnsetKeys(vec![
                "INTELX_URL".into(),
                "INTELX_KEY".into(),
            ])),
        }
    }
}

#[derive(Deserialize, Debug)]
struct SearchId {
    id: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct Query {
    term: String,
    maxresults: i32,
    media: usize,
    target: usize,
    timeout: i32,
}

impl Query {
    fn new(term: String) -> Self {
        Self {
            term,
            maxresults: 100000,
            media: 0,
            target: 1,
            timeout: 20,
        }
    }
}

#[derive(Deserialize, Debug)]
struct IntelxItem {
    selectorvalue: String,
}

#[derive(Deserialize, Debug)]
struct IntelxResults {
    selectors: Vec<IntelxItem>,
    status: usize,
}

impl IntoSubdomain for IntelxResults {
    fn subdomains(&self) -> Vec<String> {
        self.selectors
            .iter()
            .map(|s| s.selectorvalue.to_owned())
            .collect()
    }
}

#[derive(Default, Clone)]
pub struct Intelx {
    client: Client,
}

impl Intelx {
    pub fn new(client: Client) -> Self {
        Self { client }
    }

    fn build_url(
        &self,
        intelx_url: &str,
        api_key: &str,
        querying: bool,
        search_id: Option<&str>,
    ) -> String {
        if querying {
            format!("https://{}/phonebook/search?k={}", intelx_url, api_key)
        } else {
            format!(
                "https://{}/phonebook/search/result?k={}&id={}&limit=100000",
                intelx_url,
                api_key,
                search_id.unwrap()
            )
        }
    }

    async fn get_searchid(&self, host: Arc<String>) -> Result<String> {
        trace!("getting intelx searchid");
        let creds = match Creds::read_creds() {
            Ok(c) => c,
            Err(e) => return Err(e),
        };

        let query_uri = self.build_url(&creds.url, &creds.api_key, true, None);
        let body = Query::new(host.to_string());
        let search_id: SearchId = self
            .client
            .post(&query_uri)
            .json(&body)
            .send()
            .await?
            .json()
            .await?;

        debug!("searchid: {:?}", &search_id);
        Ok(search_id.id)
    }
}

#[async_trait]
impl DataSource for Intelx {
    async fn run(&self, host: Arc<String>, mut tx: Sender<Vec<String>>) -> Result<()> {
        trace!("fetching data from intelx for: {}", &host);
        let creds = match Creds::read_creds() {
            Ok(creds) => creds,
            Err(e) => return Err(e),
        };

        let search_id = self.get_searchid(host.clone()).await?;
        let uri = self.build_url(&creds.url, &creds.api_key, false, Some(&search_id));
        let resp = self.client.get(&uri).send().await?;

        if resp.status().is_client_error() {
            warn!("got status: {} for intelx", resp.status().as_str());
            return Err(VitaError::AuthError("Intelx".into()));
        } else {
            let resp: IntelxResults = resp.json().await?;
            let subdomains = resp.subdomains();
            if !subdomains.is_empty() {
                info!("Discovered {} results for: {}", &subdomains.len(), &host);
                let _ = tx.send(subdomains).await;
                return Ok(());
            }
        }

        warn!("no results for {} from Intelx", &host);
        Err(VitaError::SourceError("Intelx".into()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use matches::matches;
    use tokio::sync::mpsc::channel;

    #[tokio::test]
    #[ignore]
    async fn search_id() {
        let host = Arc::new("hackerone.com".to_owned());
        let id = Intelx::default().get_searchid(host).await.unwrap();
        assert!(!id.is_empty())
    }

    // Checks to see if the run function returns subdomains
    #[tokio::test]
    #[ignore]
    async fn returns_results() {
        let (tx, mut rx) = channel(1);
        let host = Arc::new("hackerone.com".to_owned());
        let _ = Intelx::default().run(host, tx).await;
        let mut results = Vec::new();
        for r in rx.recv().await {
            results.extend(r)
        }
        assert!(!results.is_empty());
    }

    #[tokio::test]
    #[ignore]
    async fn handle_no_results() {
        let (tx, _rx) = channel(1);
        let host = Arc::new("anVubmxpa2VzdGVh.com".to_string());
        assert!(matches!(
            Intelx::default().run(host, tx).await.err().unwrap(),
            VitaError::SourceError(_)
        ));
    }
}


text:
use crate::error::{Result, VitaError};
use crate::{DataSource, IntoSubdomain};
use async_trait::async_trait;
use reqwest::Client;
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
use tracing::{info, trace, warn};

#[derive(Debug, Deserialize)]
struct ThreatCrowdResult {
    subdomains: Option<Vec<String>>,
}

impl IntoSubdomain for ThreatCrowdResult {
    fn subdomains(&self) -> Vec<String> {
        self.subdomains
            .iter()
            .flatten()
            .map(|s| s.to_owned())
            .collect()
    }
}

#[derive(Default, Clone)]
pub struct ThreatCrowd {
    client: Client,
}

impl ThreatCrowd {
    pub fn new(client: Client) -> Self {
        Self { client }
    }

    fn build_url(&self, host: &str) -> String {
        format!(
            "https://www.threatcrowd.org/searchApi/v2/domain/report/?domain={}",
            host
        )
    }
}

#[async_trait]
impl DataSource for ThreatCrowd {
    async fn run(&self, host: Arc<String>, mut tx: Sender<Vec<String>>) -> Result<()> {
        trace!("fetching data from threatcrowd for: {}", &host);
        let uri = self.build_url(&host);
        let resp: ThreatCrowdResult = self.client.get(&uri).send().await?.json().await?;
        let subdomains = resp.subdomains();

        if !subdomains.is_empty() {
            info!("Discovered {} results for {}", &subdomains.len(), &host);
            let _ = tx.send(subdomains).await;
            return Ok(());
        }

        warn!("no results found for {} from Threatcrowd", &host);
        Err(VitaError::SourceError("ThreatCrowd".into()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use matches::matches;
    use tokio::sync::mpsc::channel;

    #[tokio::test]
    async fn returns_results() {
        let (tx, mut rx) = channel(1);
        let host = Arc::new("hackerone.com".to_owned());
        let _ = ThreatCrowd::default().run(host, tx).await;
        let mut results = Vec::new();
        for r in rx.recv().await {
            results.extend(r)
        }
        assert!(!results.is_empty());
    }

    #[tokio::test]
    async fn handle_no_results() {
        let (tx, _rx) = channel(1);
        let host = Arc::new("anVubmxpa2VzdGVh.com".to_string());
        assert!(matches!(
            ThreatCrowd::default().run(host, tx).await.err().unwrap(),
            VitaError::SourceError(_)
        ));
    }
}


text:
use crate::error::{Result, VitaError};
use crate::{DataSource, IntoSubdomain};
use async_trait::async_trait;
use reqwest::Client;
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
use tracing::{info, trace, warn};

#[derive(Debug, Deserialize)]
struct CertSpotterResult {
    dns_names: Vec<String>,
}

impl IntoSubdomain for Vec<CertSpotterResult> {
    fn subdomains(&self) -> Vec<String> {
        self.iter().flat_map(|d| d.dns_names.to_owned()).collect()
    }
}

#[derive(Default, Clone)]
pub struct CertSpotter {
    client: Client,
}

impl CertSpotter {
    pub fn new(client: Client) -> Self {
        Self { client }
    }

    fn build_url(&self, host: &str) -> String {
        format!(
            "https://api.certspotter.com/v1/issuances?domain={}\
        &include_subdomains=true&expand=dns_names",
            host
        )
    }
}

#[async_trait]
impl DataSource for CertSpotter {
    async fn run(&self, host: Arc<String>, mut tx: Sender<Vec<String>>) -> Result<()> {
        trace!("fetching data from certspotter for: {}", &host);
        let uri = self.build_url(&host);
        let resp: Option<Vec<CertSpotterResult>> =
            self.client.get(&uri).send().await?.json().await?;

        if let Some(data) = resp {
            let subdomains = data.subdomains();
            if !subdomains.is_empty() {
                info!("Discovered {} results for: {}", &subdomains.len(), &host);
                let _ = tx.send(subdomains).await;
                return Ok(());
            }
        }

        warn!("no results for {} from CertSpotter", &host);
        Err(VitaError::SourceError("CertSpotter".into()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use matches::matches;
    use tokio::sync::mpsc::channel;

    #[test]
    fn url_builder() {
        let correct_uri = "https://api.certspotter.com/v1/issuances?domain=hackerone.com\
        &include_subdomains=true&expand=dns_names";
        assert_eq!(
            correct_uri,
            CertSpotter::default().build_url("hackerone.com")
        );
    }

    // Checks to see if the run function returns subdomains
    #[tokio::test]
    async fn returns_results() {
        let (tx, mut rx) = channel(1);
        let host = Arc::new("hackerone.com".to_owned());
        let _ = CertSpotter::default().run(host, tx).await;
        let mut results = Vec::new();
        for r in rx.recv().await {
            results.extend(r)
        }
        assert!(!results.is_empty());
    }

    #[tokio::test]
    async fn handle_no_results() {
        let (tx, _rx) = channel(1);
        let host = Arc::new("anVubmxpa2VzdGVh.com".to_string());
        assert!(matches!(
            CertSpotter::default().run(host, tx).await.err().unwrap(),
            VitaError::SourceError(_)
        ));
    }
}


text:
use crate::error::{Result, VitaError};
use crate::{DataSource, IntoSubdomain};
use async_trait::async_trait;
use reqwest::Client;
use serde_json::value::Value;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
use tracing::{info, trace, warn};

struct AnubisResult {
    results: Value,
}

impl AnubisResult {
    fn new(results: Value) -> Self {
        Self { results }
    }
}

impl IntoSubdomain for AnubisResult {
    fn subdomains(&self) -> Vec<String> {
        match self.results.as_array() {
            Some(array) => array.iter().map(|s| s.to_string()).collect(),
            None => Vec::new(),
        }
    }
}

#[derive(Default, Clone)]
pub struct AnubisDB {
    client: Client,
}

impl AnubisDB {
    pub fn new(client: Client) -> Self {
        Self { client }
    }

    fn build_url(&self, host: &str) -> String {
        format!("https://jldc.me/anubis/subdomains/{}", host)
    }
}

#[async_trait]
impl DataSource for AnubisDB {
    async fn run(&self, host: Arc<String>, mut tx: Sender<Vec<String>>) -> Result<()> {
        trace!("fetching data from anubisdb for: {}", &host);
        let uri = self.build_url(&host);
        let resp: Option<Value> = self.client.get(&uri).send().await?.json().await?;

        if let Some(data) = resp {
            let subdomains = AnubisResult::new(data).subdomains();
            if !subdomains.is_empty() {
                info!("Discovered {} results for: {}", &subdomains.len(), &host);
                let _ = tx.send(subdomains).await;
                return Ok(());
            }
        }

        warn!("No results for {} from AnubisDB", &host);
        Err(VitaError::SourceError("AnubisDB".into()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use matches::matches;
    use tokio::sync::mpsc::channel;

    #[test]
    fn url_builder() {
        let correct_uri = "https://jldc.me/anubis/subdomains/hackerone.com";
        assert_eq!(correct_uri, AnubisDB::default().build_url("hackerone.com"));
    }

    // Checks to see if the run function returns subdomains
    #[tokio::test]
    async fn returns_results() {
        let (tx, mut rx) = channel(1);
        let host = Arc::new("hackerone.com".to_string());
        let _ = AnubisDB::default().run(host, tx).await.unwrap();
        let mut results = Vec::new();
        for r in rx.recv().await {
            results.extend(r)
        }
        assert!(!results.is_empty());
    }

    //TODO: should match VitaError not string message
    #[tokio::test]
    async fn handle_no_results() {
        let (tx, _rx) = channel(1);
        let host = Arc::new("anVubmxpa2VzdGVh.com".to_string());
        assert!(matches!(
            AnubisDB::default().run(host, tx).await.err().unwrap(),
            VitaError::SourceError(_)
        ));
    }
}


text:
use crate::error::{Result, VitaError};
use crate::{DataSource, IntoSubdomain};
use async_trait::async_trait;
use reqwest::Client;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
use tracing::{info, trace, warn};

const API_ERROR: &str = "error check your search parameter";

struct HTResult {
    items: String,
}

impl HTResult {
    fn new(items: String) -> Self {
        HTResult { items }
    }
}

impl IntoSubdomain for HTResult {
    fn subdomains(&self) -> Vec<String> {
        self.items
            .lines()
            .map(|s| s.split(',').collect::<Vec<&str>>()[0].to_owned())
            .collect()
    }
}

#[derive(Default, Clone)]
pub struct HackerTarget {
    client: Client,
}

impl HackerTarget {
    pub fn new(client: Client) -> Self {
        Self { client }
    }

    fn build_url(&self, host: &str) -> String {
        format!("https://api.hackertarget.com/hostsearch/?q={}", host)
    }
}

#[async_trait]
impl DataSource for HackerTarget {
    async fn run(&self, host: Arc<String>, mut tx: Sender<Vec<String>>) -> Result<()> {
        trace!("fetching data from hackertarget for: {}", &host);
        let uri = self.build_url(&host);
        let resp: String = self.client.get(&uri).send().await?.text().await?;

        if resp != API_ERROR {
            let subdomains = HTResult::new(resp).subdomains();
            info!("Discovered {} results for: {}", &subdomains.len(), &host);
            let _ = tx.send(subdomains).await;
            return Ok(());
        }

        warn!("no results found for {} from HackerTarget", &host);
        Err(VitaError::SourceError("HackerTarget".into()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use matches::matches;
    use tokio::sync::mpsc::channel;

    // Checks to see if the run function returns subdomains
    #[tokio::test]
    async fn returns_results() {
        let (tx, mut rx) = channel(1);
        let host = Arc::new("hackerone.com".to_owned());
        let _ = HackerTarget::default().run(host, tx).await;
        let mut results = Vec::new();
        for r in rx.recv().await {
            results.extend(r)
        }
        assert!(!results.is_empty());
    }

    #[tokio::test]
    async fn handle_no_results() {
        let (tx, _rx) = channel(1);
        let host = Arc::new("anVubmxpa2VzdGVh.com".to_string());
        assert!(matches!(
            HackerTarget::default().run(host, tx).await.err().unwrap(),
            VitaError::SourceError(_)
        ));
    }
}


text:
use crate::error::{Result, VitaError};
use crate::{DataSource, IntoSubdomain};
use async_trait::async_trait;
use reqwest::Client;
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
use tracing::{info, trace, warn};

#[derive(Deserialize, Hash, Eq, PartialEq)]
struct UrlScanPage {
    page: UrlScanDomain,
}

#[derive(Deserialize, Eq, Hash, PartialEq)]
struct UrlScanDomain {
    domain: String,
}

#[derive(Deserialize)]
struct UrlScanResult {
    results: Vec<UrlScanPage>,
}

impl IntoSubdomain for UrlScanResult {
    fn subdomains(&self) -> Vec<String> {
        self.results
            .iter()
            .map(|s| s.page.domain.to_owned())
            .collect()
    }
}

#[derive(Default, Clone)]
pub struct UrlScan {
    client: Client,
}

impl UrlScan {
    pub fn new(client: Client) -> Self {
        Self { client }
    }

    fn build_url(&self, host: &str) -> String {
        format!("https://urlscan.io/api/v1/search/?q=domain:{}", host)
    }
}

#[async_trait]
impl DataSource for UrlScan {
    async fn run(&self, host: Arc<String>, mut tx: Sender<Vec<String>>) -> Result<()> {
        trace!("fetching data from urlscan for: {}", &host);
        let uri = self.build_url(&host);
        let resp: Option<UrlScanResult> = self.client.get(&uri).send().await?.json().await?;

        if let Some(data) = resp {
            let subdomains = data.subdomains();
            if !subdomains.is_empty() {
                info!("Discovered {} results for: {}", &subdomains.len(), &host);
                let _ = tx.send(subdomains).await;
                return Ok(());
            }
        }

        warn!("no results found for {} from UrlScan", &host);
        Err(VitaError::SourceError("UrlScan".into()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use matches::matches;
    use tokio::sync::mpsc::channel;

    #[test]
    fn url_builder() {
        let correct_uri = "https://urlscan.io/api/v1/search/?q=domain:hackerone.com";
        assert_eq!(correct_uri, UrlScan::default().build_url("hackerone.com"));
    }

    #[tokio::test]
    async fn returns_results() {
        let (tx, mut rx) = channel(1);
        let host = Arc::new("hackerone.com".to_owned());
        let _ = UrlScan::default().run(host, tx).await;
        let mut results = Vec::new();
        for r in rx.recv().await {
            results.extend(r)
        }
        assert!(!results.is_empty());
    }

    #[tokio::test]
    async fn handle_no_results() {
        let (tx, _rx) = channel(1);
        let host = Arc::new("anVubmxpa2VzdGVh.com".to_string());
        assert!(matches!(
            UrlScan::default().run(host, tx).await.err().unwrap(),
            VitaError::SourceError(_)
        ));
    }
}


text:
use crate::error::{Result, VitaError};
use crate::{DataSource, IntoSubdomain};
use async_trait::async_trait;
use dotenv::dotenv;
use reqwest::header::ACCEPT;
use reqwest::Client;
use serde::Deserialize;
use std::env;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
use tracing::{info, trace, warn};

struct Creds {
    token: String,
}

impl Creds {
    pub fn read_creds() -> Result<Self> {
        dotenv().ok();
        match env::var("SPYSE_TOKEN") {
            Ok(token) => Ok(Self { token }),
            Err(_) => Err(VitaError::UnsetKeys(vec!["SPYSE_TOKEN".into()])),
        }
    }
}

#[derive(Debug, Deserialize)]
struct SpyseResult {
    data: SpyseItem,
}

#[derive(Debug, Deserialize)]
struct SpyseItem {
    items: Vec<Subdomain>,
}

#[derive(Debug, Deserialize)]
struct Subdomain {
    name: String,
}

impl IntoSubdomain for SpyseResult {
    fn subdomains(&self) -> Vec<String> {
        self.data.items.iter().map(|i| i.name.to_owned()).collect()
    }
}

#[derive(Default, Clone)]
pub struct Spyse {
    client: Client,
}

impl Spyse {
    pub fn new(client: Client) -> Self {
        Self { client }
    }

    fn build_url(&self, host: &str) -> String {
        format!(
            "https://api.spyse.com/v3/data/domain/subdomain?limit=100&domain={}",
            host
        )
    }
}

#[async_trait]
impl DataSource for Spyse {
    async fn run(&self, host: Arc<String>, mut tx: Sender<Vec<String>>) -> Result<()> {
        trace!("fetching data from spyse for: {}", &host);
        let token = match Creds::read_creds() {
            Ok(creds) => creds.token,
            Err(e) => return Err(e),
        };

        let uri = self.build_url(&host);
        let resp = self
            .client
            .get(&uri)
            .header(ACCEPT, "application/json")
            .bearer_auth(token)
            .send()
            .await?;

        if resp.status().is_client_error() {
            warn!("got status: {} from spyse", resp.status().as_str());
            return Err(VitaError::AuthError("Spyse".into()));
        } else {
            let resp: Option<SpyseResult> = resp.json().await?;
            if let Some(data) = resp {
                let subdomains = data.subdomains();
                info!("Discovered {} results for {}", &subdomains.len(), &host);
                let _ = tx.send(subdomains).await;
                return Ok(());
            }
        }

        warn!("no results for {} from Spyse", &host);
        Err(VitaError::SourceError("Spyse".into()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use matches::matches;
    use tokio::sync::mpsc::channel;

    #[test]
    fn url_builder() {
        let correct_uri =
            "https://api.spyse.com/v3/data/domain/subdomain?limit=100&domain=hackerone.com";
        assert_eq!(correct_uri, Spyse::default().build_url("hackerone.com"));
    }

    // Checks to see if the run function returns subdomains
    #[ignore]
    #[tokio::test]
    async fn returns_results() {
        let (tx, mut rx) = channel(1);
        let host = Arc::new("hackerone.com".to_owned());
        let _ = Spyse::default().run(host, tx).await;
        let mut results = Vec::new();
        for r in rx.recv().await {
            results.extend(r)
        }
        assert!(!results.is_empty());
    }

    #[ignore]
    #[tokio::test]
    async fn handle_no_results() {
        let (tx, _rx) = channel(1);
        let host = Arc::new("anVubmxpa2VzdGVh.com".to_string());
        assert!(matches!(
            Spyse::default().run(host, tx).await.err().unwrap(),
            VitaError::SourceError(_)
        ));
    }
}


text:
use crate::error::{Result, VitaError};
use crate::{DataSource, QUEUE_SIZE};
use async_trait::async_trait;
use crobat::Crobat;
use futures::StreamExt;
use reqwest::Client;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
use tracing::{debug, info};

#[derive(Default, Clone)]
pub struct SonarSearch {
    client: Client,
}

impl SonarSearch {
    pub fn new(client: Client) -> Self {
        Self { client }
    }
}

#[async_trait]
impl DataSource for SonarSearch {
    async fn run(&self, host: Arc<String>, mut tx: Sender<Vec<String>>) -> Result<()> {
        let mut results = Vec::with_capacity(QUEUE_SIZE);
        let mut client = Crobat::connect().await?;
        let mut subs = client.get_subs(host.clone()).await?;

        while let Some(r) = subs.next().await {
            let domain = r.map(|d| d.domain).map_err(|_| VitaError::CrobatError)?;
            results.push(domain);

            if results.len() == QUEUE_SIZE {
                debug!("sonarsearch queue is full, sending across channel",);
                let mut tx = tx.clone();
                let _ = tx.send(results.drain(..).collect()).await;
            }
        }

        if !results.is_empty() {
            info!(
                "draining {} remaining items from sonarsearch queue",
                results.len()
            );
            let _ = tx.send(results.drain(..).collect()).await;
        }

        Err(VitaError::SourceError("SonarSearch".into()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use matches::matches;
    use tokio::sync::mpsc::channel;

    #[ignore]
    #[tokio::test]
    async fn returns_results() {
        let (tx, mut rx) = channel(1);
        let host = Arc::new("hackerone.com".to_owned());
        let _ = SonarSearch::default().run(host, tx).await;
        let mut results = Vec::new();
        for r in rx.recv().await {
            results.extend(r)
        }
        assert!(!results.is_empty());
    }

    #[ignore]
    #[tokio::test]
    async fn handle_no_results() {
        let (tx, _rx) = channel(1);
        let host = Arc::new("anVubmxpa2VzdGVh.com".to_owned());
        assert!(matches!(
            SonarSearch::default().run(host, tx).await.err().unwrap(),
            VitaError::SourceError(_)
        ));
    }
}


text:
use crate::error::{Result, VitaError};
use crate::{DataSource, IntoSubdomain};
use async_trait::async_trait;
use dotenv::dotenv;
use reqwest::Client;
use serde::Deserialize;
use std::env;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
use tracing::{info, trace, warn};

struct Creds {
    api_key: String,
}

impl Creds {
    pub fn read_creds() -> Result<Self> {
        dotenv().ok();
        match env::var("SECURITY_TRAILS_KEY") {
            Ok(api_key) => Ok(Self { api_key }),
            Err(_) => Err(VitaError::UnsetKeys(vec!["SECURITY_TRAILS_KEY".into()])),
        }
    }
}

#[derive(Debug, Deserialize, Default)]
struct SecTrailsResult {
    subdomains: Vec<String>,
    #[serde(skip)]
    host: Arc<String>,
}

impl IntoSubdomain for SecTrailsResult {
    fn subdomains(&self) -> Vec<String> {
        self.subdomains
            .iter()
            .map(|s| format!("{}.{}", s, self.host))
            .collect()
    }
}

#[derive(Default, Clone)]
pub struct SecurityTrails {
    client: Client,
}

impl SecurityTrails {
    pub fn new(client: Client) -> Self {
        Self { client }
    }

    fn build_url(&self, host: &str) -> String {
        format!(
            "https://api.securitytrails.com/v1/domain/{}/subdomains",
            host
        )
    }
}

#[async_trait]
impl DataSource for SecurityTrails {
    async fn run(&self, host: Arc<String>, mut tx: Sender<Vec<String>>) -> Result<()> {
        trace!("fetching data from securitytrails for: {}", &host);

        let api_key = match Creds::read_creds() {
            Ok(creds) => creds.api_key,
            Err(e) => return Err(e),
        };

        let uri = self.build_url(&host);
        let resp = self
            .client
            .get(&uri)
            .header("apikey", api_key)
            .send()
            .await?;

        if resp.status().is_client_error() {
            warn!(
                "got status: {} from security trails",
                resp.status().as_str()
            );
            return Err(VitaError::AuthError("SecurityTrails".into()));
        } else {
            let resp: Option<SecTrailsResult> = resp.json().await?;
            if let Some(data) = resp {
                let subdomains = data.subdomains();
                info!("Discovered {} results for: {}", &subdomains.len(), &host);
                let _ = tx.send(subdomains).await;
                return Ok(());
            }
        }

        warn!("no results for {} from SecurityTrails", &host);
        Err(VitaError::SourceError("SecurityTrails".into()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use matches::matches;
    use tokio::sync::mpsc::channel;

    #[test]
    fn url_builder() {
        let correct_uri = "https://api.securitytrails.com/v1/domain/hackerone.com/subdomains";
        assert_eq!(
            correct_uri,
            SecurityTrails::default().build_url("hackerone.com")
        );
    }

    // Checks to see if the run function returns subdomains
    #[ignore]
    #[tokio::test]
    async fn returns_results() {
        let (tx, mut rx) = channel(1);
        let host = Arc::new("hackerone.com".to_owned());
        let _ = SecurityTrails::default().run(host, tx).await;
        let mut results = Vec::new();
        for r in rx.recv().await {
            results.extend(r)
        }
        assert!(!results.is_empty());
    }

    // TODO: Test assumes credentials from env are valid.
    #[ignore]
    #[tokio::test]
    async fn handle_no_results() {
        let (tx, _rx) = channel(1);
        let host = Arc::new("anVubmxpa2VzdGVh.com".to_string());
        assert!(matches!(
            SecurityTrails::default().run(host, tx).await.err().unwrap(),
            VitaError::SourceError(_)
        ));
    }
}