User Guide

Interactive guide to using your Yetibot

Tip

This guide is all about interacting with Yetibot from the user's perspective. It documents runtime usage and many of the available commands.

  • If you're more interested in Yetibot's internals or building features, check out the Developer Guide.
  • If you're interested in running and operating your own instance of Yetibot, check out the Operations Guide.

Getting help

Although we hope the docs are useful and comprehensive, don't hesitate to ask for help in Slack! It could potentially save you a lot of time.

All the examples below are interactive. Click the button below each example evaluate the expression against the live, public Yetibot instance using the GraphQL API.

Basic Yetibot expressions

This is what a simple command looks like:

!echo Hello World!

Yetibot should reply with Hello World!.

To see a more dynamic example, run uptime:

!uptime

Help

The help command lets you explore the commands available and their usage. It has its own help doc:

!help help
!help

Response Types

Yetibot responses are either:

  1. A single value
  2. A collection of values

There are no other types in Yetibot, though the underlying Clojure data structures in responses may contain more diverse responses, such as when returning the result of an API call.

Here's an example of a collection:

!list 1 2 3

To view the raw underlying Clojure data structure type of a response in Yetibot, use raw. For example:

!list 1 2 3 | raw

Pipes

Pipes allow chaining together more complex expressions using the output of one command as the input of another command.

!echo 1 | echo 2 | echo 3

Notice how the output of each preceding command is appended to the end of the following command separated by a space. This is the default behavior, but you can also choose where to place arguments and control whitespace using %s:

!echo foo | echo %sbar

Subexpressions

Subexpressions let you build up more complex expressions by nesting one expression in another.

!echo `echo Hello` Yetibot

Backticks are convenient when you need a single level of nesting, but $() syntax lets you embed any level of nesting:

!echo $(echo $(echo Yetibot) is) `echo alive`

These examples are necessarily simplistic but when you start piecing together more complex commands that fetch data, the ability to arbitrarily nest expressions is quite useful.

Fun 🎉

Fun has always been an important part of Yetibot's existence. The full list of commands in the fun category can be listed via:

!category list fun

Below we document a few highlights.

Meme generation

Meme gen was one of Yetibot's first features 😂.

!meme yetibot: hello world!
!help meme
!catfact | meme
!chuck | meme chuck:
!jargon | meme yeah if you:

Giphy

!help giphy
!giphy reaction
!giphy whoa
!giphy random
!giphy trending

No

!help no
!no

Http

!http 404
!http 200
!http 401
!http 500
!http 420

Collection utilities

Since Yetibot can return a collection as a response, it needs basic operations to manipulate collections.

range

!help range
!range 10
!range 0 100 25

list

!help list
!list Yetibot is alive

xargs

xargs allows us to operate over a collection, evaluating an expression for each value, in parallel.

!help xargs
!range 10 | xargs echo number | unwords

random

!help random

With no args, random just produces a random number:

!random

But if you pass it a collection it takes a random item from it:

!range 10 | random
!help head
!range 10 | head
!range 10 | head 3

tail

!help tail
!range 10 | tail 3
!range 10 | head 3 | tail

shuffle

!help shuffle
!range 10 | shuffle

words

!help words
!echo Delta compression using up to 8 threads | words

unwords

!help unwords
!range 10 | unwords
!echo Delta compression using up to 8 threads | words | shuffle | unwords

join

!help join
!range 10 | join
!range 10 | join 💥

split

!help split
!echo stick-in-the-mud | split -

letters

This is just like words, except it splits apart letters.

!help letters
!echo Yetibot | letters

unletters

!help unletters
!echo Yetibot | letters | shuffle | unletters

trim

!help trim
!echo    why such space    | trim

set

!help set
!list 1 1 2 2 3 3 | set

count

!help count
!range 10 | count

sum

!help sum
!range 5 | sum

It doesn't like it if you try to sum non-numbers:

!list one two three | sum

sort

!help sort
!list foo bar baz | sort

sortnum

!help sortnum
!list 22 foos, 33 bars, 11 bazes | sortnum

grep

!help grep
!range 50 | grep 5
!range 50 | grep -C 1 5

tee

!help tee

tee doesn't work in the GraphQL API yet, but you can try this out via chat:

!echo foo | tee | echo bar

reverse

!help reverse
!range 3 | reverse

droplast

!help droplast
!list first middle last | droplast

rest

!help rest
!range 10 | rest

Other collection commands

There are a few others not documented here such as raw, keys, vals`, and data (which is documented in its own section of this guide). To view them all we can look them up by category:

!category list collection

Aliases

Aliases are similar to alias in bash allowing us to give a name for a command. This is typically a heavily-used feature as teams build up aliases for fun or utility and become a manifestation of culture.

!help alias

As an example, look at the default output of stock:

!stock tsla

Let's say we wanted a more concise one-liner using its data:

!stock tsla | data show
!alias quickstock = 'stock $s | render {% ifequal change-percent|first "-" %}📉{% else %}📈{% endifequal %} {{symbol}} ${{price}} {{change-percent}} / ${{change}}'

Let's get multiple stocks at once:

!list aapl tsla vxus | xargs quickstock

Observers

An observer listens for patterns and automatically runs commands when triggered. They're super powerful but can easily be abused.

!help observer

Run the above help docs. As you can see, observers support several different event types.

message

The default event type for observers is message. This allows Yetibot to react to a message by running a command. For example:

!obs sushi = react :sushi:

With this observer any time anyone mention sushi they get a 🍣 reaction. Note that we could have specified a regex pattern instead of the literal "sushi".

It also supports optional settings that let us filter on:

  • the name of the channel using -c
  • the username of the person who posted a message using -u

For example:

!obs -c dev-testing -u devth = echo {{username}} said {{body}} in {{channel}}

The above example also illustrates the templating functionality. Along with username and body, channel is available on all event types, and for react observer events, reaction and message-username are also available.

react

A react event fires when a user reacts to a message (Slack only).

!obs -c dev-testing -e react = giphy {{reaction}} {{body}}

This causes a giphy lookup using the reaction used (e.g. 100 or smile) and the body text of the message that was reacted to.

React events also have two other fields available: reaction and message-username where message-username is the username of the user that posted the original message (username is the username of the user that reacted).

enter

The enter event fires when a user enters a channel.

!obs -c dev-testing -e enter = meme internet: welcome to {{channe}} {{username}}!

leave

The leave event fires when a user leaves a channel.

!obs -c dev-testing -e leave = meme crying jordan: / bye {{username}}

Categories

Yetibot commands can be organized under categories. Commands can be enabled or disabled according to channel settings (e.g. :fun). In the future, help might rely on categories.

Categories are stored as meta-data directly on command handler functions under a :yb/cat prefix with a Set of keywords as the value.

Current known categories are as follows. Please add to this list as needed. Some categories will overlap but are semantically distinct.

!help category
!category
!category list fun
!category list util
!category list repl

Channel-based category toggle

Each category can be disabled or enabled at the channel level. By default all categories are enabled. To disable them, use:

!category disable :category-name

NB

Disabled categories are stored using the normal channel settings, so you'll see them in !room if you set them. !category is merely a convenience wrapper.

Show the list of categories and their docs:

!category

Disable the "fun" category:

!category disable fun

Re-enable it:

!category enable fun

SSH

Yetibot can ssh into servers and run commands on your behalf.

The config looks like:

{:yetibot
 {;; ...

  :ssh {:groups
        [{:user "",
          :servers [{:host "", :name ""} {:host "", :name ""}],
          :key "path-to-key"}]},
 }}

Each map inside the :groups collection has a single user and key and allows multiple servers.

For example, if we had a server with name example we could see it in the list of servers via:

!ssh servers

and run commands via:

!ssh example echo foo

Yetibot ssh currently only supports key-based authentication.

Utilities

Data

Yetibot commands by default return a pretty-printed response for human consumption, but for many commands the underlying data is preserved and passed across the pipe as well:

!help data

For example, we can get all the data behind the weather command:

!weather seattle, wa | data show

Render

The render command lets us turn the data discussed in the previous section into strings, which means we can customize the output of a command using the data behind it!

Using our weather example again:

!weather seattle | render Wind in {{city_name}} at {{wind_spd|multiply:2.23694|double-format:2}} mph blowing {{wind_cdir_full|capitalize}}

The templating support is provided by Selmer. It supports a number of filters, e.g. {{name|capitalize}}. See the Selmer docs for more!

Need to know the ID of the computer gandalf meme for some reason?

!meme search computer gandalf | data show
!meme search computer gandalf | render ID of {{name}} at {{url}} is {{id}}

We could go on and on (and on and on). render opens up a ton of possabilities for customizing command output and using Yetibot in unexpected and unanticipated ways!

Cron

Cron is, as you'd expect, a way to run a command on an interval given a cron expression.

!help cron

Eval

The eval command runs arbitrary Clojure against the Yetibot process itself, so it's by definition very insecure. Because of this, it's only allowed to be run by users that have been pre-configured to have access.

It can be a fun way of poking at otherwise-unavailable state inside Yetibot.

!help eval

Scrape

!help scrape

Channel settings

Arbitrary key/value pairs can be stored on a per-channel basis. This lets you do things like set channel local JIRA projects, Jenkins jobs, or other values that could for example be utilized by aliases.

!help channel

That

The that command retrieves the last non-command thing said, excluding Yetibot output, and that cmd retrieves the last command.

!help that

Example usage:

!that | meme insanity:

Karma

The karma command lets you increment or decrement another user's karma along with an optional note.

!help karma

View the leaderboard:

!karma

There are three ways to adjust a user's Karma.

The canonical !karma command invocation, using the traditional C increment (++) or decrement () operators, optionally followed by a note.

!karma @yetibot++ thanks Yetibot
!karma @yetibot-- you had one job

The Emoji versions to add (🌈) or subtract (⛈), optionally followed by a note.

🌈 @yetibot
⛈ @yetibot I told you to go before we got in the car!

Via Emoji reactions, which necessarily preclude the addition of notes.

karma screenshot

At this time removing your Emoji reaction does not reverse the initial addition or subtraction of Karma.

In all cases, Yetibot will respond with the user's new score. Emoji reaction Karma is reported in a thread.

Now take a look at Yetibot's Karma:

!karma @yetibot

In addition to these commands, karma is also exposed via the GraphQL API!

Try a query like:

# scores
curl -s 'https://public.yetibot.com/graphql' \
  -H 'Accept: application/json' \
  --data 'query={karmas(report: SCORES) {user_id score}}' \
  --compressed | jq

# givers
curl -s 'https://public.yetibot.com/graphql' \
  -H 'Accept: application/json' \
  --data 'query={karmas(report: GIVERS) {user_id score}}' \
  --compressed | jq

# inspect Yetibot's karma
curl -s 'https://public.yetibot.com/graphql' \
  -H 'Accept: application/json' \
  --data 'query={user(id:"U3K6P707R"){username,karma}}' \
  --compressed | jq

You can configure the positive and negative Emojis used in commands and reactions, overriding the default 🌈 and ⛈. Have a look at config/config.sample.edn.

Please use Slack shortcodes and not the Unicode characters themselves.

:karma {:emoji {:positive ":taco:" :negative ":poop:"}}}}

GitHub

!help gh

List the open PRs for Yetibot:

!gh pr yetibot

or try Clojure:

!gh pr clojure

List Yetibot's repos:

!gh repos yetibot

List the tags on yetibot.core:

!gh tags yetibot/yetibot.core

List contributors on the yetibot/yetibot repo since last month:

!gh contributors yetibot/yetibot since 1 month ago

Look up git change stats on Clojure:

!gh stats clojure/clojure

Look up git change stats on Yetibot's first 3 repos:

!gh repos yetibot | head 3
!gh repos yetibot | head 3 | xargs words | xargs head | xargs gh stats

JIRA

Yetibot's JIRA capabilities are pretty powerful, especially when combined with Yetibot's expressions pipeline capabilities. It can operate over a global list of projects or per-channel project settings. Operations include creating, updating, resolving, describing, deleting, assigning, logging work, and commenting on issues.

It can also search issues using JIRA's powerful jql syntax.

!help jira

JIRA Basics

Let's start by creating a new issue:

!jira create new issue from yetibot.com!

If we list recent issues and you should see the issue you just created at the top:

!jira recent

We can get more details on that issue:

!jira recent | head | jira parse | jira show

Let's break this down keeping in mind values flow across | pipes:

  1. jira recent - list the recent issues
  2. head - take the first issue from the list
  3. jira parse - extract the issue key from a url, e.g. extract YETIBOT-X from https://yetibot.atlassian.net/browse/YETIBOT-X
  4. jira show - describe the issue

If we knew the issue key ahead of time, we could have simply used it:

!jira show YETIBOT-4

We could also parse issue keys out of all recent issues via:

!jira recent | xargs jira parse

What if you want to create a subtask? We can do that too using the -p option to specify the parent when creating:

!help jira | grep create

Create a sub-task of our most recent issue:

!jira recent | head | jira parse | jira create I am a subtask -p %s

JIRA JQL

If you want to quickly search for issues:

!jira search demo

This runs a JQL query that looks like:

project in (YETIBOT) AND
  (summary ~ "demo" OR description ~ "demo" OR comment ~ "demo")

It searches across 3 fields:

  1. summary
  2. description
  3. comment

But you can also drop down to raw JQL when the need arises:

!jira jql created >= "-5h"

Checkout Atlassian's Advanced searching docs and Advanced searching - operators reference for more info on using JQL. It's super powerful.

Get fancy with JIRA

Let's look at some of the ways we can interact with our issue with the power of Yetibot expressions.

!jira recent | head | jira parse | jira comment %s Works on my machine

We can also assign issues, but first let's see who's all available:

!jira project-users

Note: we can also search for users, system wide:

!jira users t
!jira users t | data show

Assign a random issue to a random user (try running this multiple times for extra chaos 😈):

!jira recent | random | jira parse | jira assign %s `jira project-users | random`

Or assign all issues that mention docs in the summary to Yetibot:

!jira jql summary ~ docs | xargs jira parse | xargs jira assign %s Yetibot

Let's add a worklog item:

!jira recent | random | jira parse | jira worklog %s 15m explored the docs

Components, versions, issue types, projects

List components:

!jira components

All of the JIRA commands expose data. For example, take a peek at the full API response for a component:

!jira components | head | data show

This gives us flexability to get at any piece of the data that might not be included in Yetibot's default formatted output. What if we wanted to get the avatar of the first component's lead?

!jira components | head | render {{lead.avatarUrls.48x48}}

We can also list things like versions, issue types, and projects:

!jira versions
!jira issue-types
!jira projects

Search for projects matching yet:

!jira projects yet

Peek at some raw data behind the pipe:

!jira projects | random | data show

We can combine any of these with Yetibot's pipeline capabilities and do batch operations.

JIRA batch combos

!list do the right thing, do the best thing, do the fastest thing | xargs jira create
!jira jql summary ~ thing AND resolution = Unresolved
!jira jql summary ~ thing AND resolution = Unresolved | random | jira parse | jira comment %s I `list agree,disagree | random`

Batch resolve all the things:

!jira jql summary ~ thing AND resolution = Unresolved | xargs render {{key}}
!jira jql summary ~ thing AND resolution = Unresolved | xargs render {{key}} | xargs jira resolve %s Fixed

Now clean your JIRA with jira delete:

!jira jql summary ~ thing AND resolution = Done | xargs jira parse | xargs jira delete

🔥

And if you want to really clean up, delete all issues created today:

!jira jql created >= startOfDay()
!jira jql created >= startOfDay() | xargs jira parse | xargs jira delete

🔥😑

JIRA Configuration

JIRA configuration looks like:

  :jira {:default {:project {:key "Optional"}, :issue {:type {:id "3"}}},
         :user "",
         :projects [{:default {:version {:id "42"}}, :key "FOO"}],
         :subtask {:issue {:type {:id "27"}}},
         :domain "",
         :password ""},

The projects key is optional and specifies the global list of projects. This is mostly important for the observer that detects a JIRA task like PROJ-123 and auto outputs the details of the task, when found.

This ability is also configurable on a per-channel basis using channel settings:

!channel set jira-project YETIBOT,COM

After setting that in a channel, Yetibot will know to expand all occurrences of strings that look like JIRA keys, such as YETIBOT-123 or COM-2.

GraphQL API

Yetibot serves a GraphQL endpoint as part of its web app. The public Yetibot instance is accessible at https://public.yetibot.com/graphql.

For example, to inspect the configured adapters, run the following query with a GraphQL client:

{
  adapters {
    uuid
    platform
  }
}

Or if you prefer examples in curl:

# list the configured Adapters
curl -s 'https://public.yetibot.com/graphql' \
  -H 'Accept: application/json' \
  --data 'query={adapters {uuid platform}}' \
  --compressed | jq

# get some stats about this Yetibot
curl -s 'https://public.yetibot.com/graphql' \
  -H 'Accept: application/json' \
  --data 'query={stats {uptime adapter_count user_count command_count}}' \
  --compressed | jq

Recipes

These recipes are fun example usages of Yetibot that more holistically communicate its capabilities and historical usage.

Celcius / Fahrenheit conversion with render

The built in render command is quite powerful because it uses the Selmer library for rendering templates, which includes many filters for postprocessing a value.

The following example illustrates this by implementing Celcius to Fahrenheit conversion, and vice versa, in terms of render:

!alias ctof = "echo $s°C = `render {{$s|multiply:1.8|add:32|round}}°F`"
!alias ftoc = "echo $s°F = `render {{$s|add:-32|multiply:5|divide:9|round}}°C`"
!ctof 0
!ftoc 100

Countdown

This alias uses JavaScript to determine the number of days till a specific date and outputs the day along with a string message. It could be further abstracted as a countdown alias that takes a date and a message, but we typically just re-define the alias as needed.

!alias countdown = "js Math.ceil((Date.parse('4/2/2019') - Date.now()) / (1000 * 60 * 60 * 24)) + ' days until Google+ shutdown'"
!countdown

Alltemps

We use an alias to store the list of zip codes for all members of our distributed team, which we then feed to another alias which outputs a short single-line description of current weather for a location.

!alias locs = list 59101 98101 94101

!alias loctemp = "weather $s | xargs sed s/Current conditions for / | head 2 | reverse | unwords"

!alias alltemps = "locs | xargs loctemp | sort"

!alltemps

25.4 F (-3.7 C), Mostly Cloudy Jupiter Sulphur, Billings, Montana elevation 3114 ft:
43.2 F (6.2 C), Partly Cloudy Belltown, Seattle, Washington elevation 135 ft:
49.8 F (9.9 C), Overcast SOMA - Near Van Ness, San Francisco, California elevation 49 ft:

Yetibot should not have kids

I'm pretty fond of this one. I needed really wanted a way to seed a meme alias with valid sentences like:

[user] should not [do something]

Time for some Google Complete, JavaScript, and a few aliases!

First, we need a random letter to seed Google Complete with, for ultimate variety of "shoulds":

!alias randletter = "range 65 91 | xargs echo | random | js String.fromCharCode(%s)"

!repeat 5 randletter | unwords

V L R U S

LGTM! ☝️

Now pipe that to a well-crafted complete query:

!randletter | complete should i | random | sed s/should i/

invest in bonds

Awesome, let's alias it:

!alias randshould = "randletter | complete should i | random | sed s/should i/"

!repeat 10 randshould

take creatine
call her
max out my 401k
move to san francisco
shave my beard
have kids
buy a house
do it
feed feral cats
have a savings account

Finally:

!alias usershouldnot = "user | random | echo %s should not `randshould`"

!usershouldnot | meme angry wolf:

yetibot should not

Or try the variant, yetishould:

!yetishould

Hint: run it more than once 😁

Grids

!alias dis = echo ಠ_ಠ

!alias grid = "repeat 3 echo $(repeat 3 echo $s | unwords)"

!grid `dis`

ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ

!grid `dis` | xargs grid

ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ

!grid `dis` | xargs grid | xargs xargs grid

ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ
ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ ಠ_ಠ

I think you get the idea 🧐

🤔

Edit this page