public static void setButtonBackground(Button btn, int normal_color, int press_color) {
StateListDrawable res = new StateListDrawable();
res.addState(new int[]{android.R.attr.state_pressed}, setGradientColors(normal_color));
res.addState(new int[]{}, setGradientColors(press_color));
btn.setBackground(res);
}

public static GradientDrawable setGradientColors(int color) {
GradientDrawable gradient = new GradientDrawable();
gradient.setShape(GradientDrawable.RECTANGLE);
gradient.setColor(color);
gradient.setCornerRadius(120.f);
return gradient;
}


RiveScript 기본적인 Tutorial을 끝냈습니다. 지금까지 RiveScript 코드 작성 방법에 대해 배웠으며 이를 토대로 chatterbot을 개발하는데 도움이 되었으면 합니다. 우리가 다루지 못한 내용으로는 Object Macros가 있지만 이에 대한 자세한 내용은 https://www.rivescript.com/docs/tutorial#object-macros 을 참조하십시오. 우리는 튜토리얼에서 배운 내용을 바탕으로 안드로이드에서 사용 가능한 챗봇을 개발하는 부분에 대해서 실습할 수 있도록 하겠습니다. 감사합니다.

'프로그래밍 > RiveScript' 카테고리의 다른 글

[Tutorial] CONCLUSION  (0) 2017.03.09
[Tutorial] TAGS  (0) 2017.03.09
[Tutorial] MORE DEFINITIONS  (0) 2017.03.09
[Tutorial] The Begin Block  (0) 2017.03.09
[Tutorial] Topics  (0) 2017.03.09
[Tutorial] Conditionals  (0) 2017.03.09

지금까지 우리는 RiveScript의 TAG에 대해서 공부 했습니다. 

우리가 공부했던  태그의 예는 <star><get>, {topic}입니다.

실제로는 이 보다 더 많은 태그가 있고 우리는 이를 사용할 수 있습니다!

이번에는 우리가 배우지 않은 태그와 태그 사용 방법을 배워보겠습니다. 

태그는 일반적으로 화살괄호(<>)안에 태그를 입력하는 형태로 사용이 되며 중괄호({})는 주변의 텍스를 수정하는데 사용이 됩니다. 태그는 일반적으로 특별히 명시된 경우를 제외하고 모든 RiveScript 명령과 함께 사용할 수 있습니다.

  • <botstar><botstar1> - <botstarN>

이 <star>태그는 와 비슷하지만 % Previous에 있는 와일드 카드를 가져옵니다. 다음은 그 예입니다.

+ i bought a new *
- Oh? What color is your new <star>?

+ (@colors)
% oh what color is your new *
- <star> is a pretty color for a <botstar>.

실제로 실행해보면 <botstar>태그는 이전의 트리거에 표시된 즉 %에 사용된 와일드카드를 가지고 오게 됩니다. "i bought a new car"라고 입력하게 되면 car = <star>가 됩니다. 그리고 "blue"라고 컬러 값을 입력 했을 때 blue = <star>가 되지만 이전에 car = <botstar>가 되게 됩니다. 잘 이해가 안 될 수도 있지만 자주 사용하다보면 어떤 식으로 구동하는지 잘 알 수 있을 것입니다. 물론 와일드 카드 수에 따라서 <botstar1>로 사용할 수 있습니다.

  • <input><reply>

<input>, <reply> 태그는 사용자 및 봇이 각각 보낸 이전 메시지를 표시하는데 사용됩니다. 이전의 9개 메시지까지가 저장이 되며 <input1> 에서 <input9>까지 <reply1>에서 <reply9>까지 사용이 가능합니다. 다음은 태그가 사용되는 몇가지 예제입니다.

// If the user repeats the bot's previous message
+ <reply>
- Don't repeat what I say.

// If the user keeps repeating themselves over and over.
+ <input1>
* <input1> == <input2> => That's the second time you've repeated yourself.
* <input1> == <input3> => If you repeat yourself again I'll stop talking.
* <input1> == <input4> => That's it. I'm not talking.{topic=sorry}
- Please don't repeat yourself.

// An example that uses both tags
+ why did you say that
- I said, "<reply>", because you said, "<input>".
  • <id>

<id>는 기본적으로  localuser이며 RiveScript는 사용자 ID를 통해서 여러 사용자를 구분합니다. 동일한 RiveScript 인터프리터를 사용하여 여러 사용자에 대한 응답을 제공 할 수 있으며 ID를 기반으로 사용자 변수를 별도로 유지합니다. 다음은 일반 사용자와 관리자를 구분하는 방법의 예입니다.

! var master = kirsle

+ am i your master
* <id> == <bot master> => Yes, you are. Hi Kirsle!
- No, <bot master> is my master, and you are <id>.
  • <bot>

<bot>태그는 봇 변수를 검색하는데 사용됩니다. 또한 봇 변수를 설정하는데 사용할 수도 있습니다. 봇 변수는 RiveScript 인터프리터 인스턴스에 대해 "전역"으로 간주 될 수 있습니다. 즉, 봇의 이름을 Aiden으로 설정하면 사용자의 ID와 상관없이 요청하는 모든 사람의 이름이 Aiden이됩니다. 이것은 특정 사용자 ID에 연결된 사용자 변수와 대조됩니다. 

+ what is your name
- You can call me <bot name>.

+ tell me about yourself
- I am <bot name>, a chatterbot written by <bot master>.

// Setting a bot variable dynamically
+ i hate you
- Aww! You've just ruined my day.<bot mood=depressed>
  • <env>

<env> 태그는 전역 변수를 검색하는 데 사용됩니다. 또한 전역 변수를 설정할 때도 사용할 수 있습니다.

예를 들어 RiveScript 인터프리터가 모든 환경 변수를 RiveScript 전역으로 복사하면 CGI 기반 RiveScript 봇이 사용자에게 자신의 IP 주소를 알릴 수 있습니다.

+ what is my ip
- Your IP address is: <env REMOTE_ADDR>

다음은 전역변수를 설정하는 방법에 대한 예제입니다. 이 예제에서 관리자는 디버그 모드를 켜거나 끌 수 있습니다.

+ set debug mode (true|false)
* <id> == <bot master> => <env debug=<star>>Debug mode set to <star>.
- You're not my master.
  • <add><sub><mult><div>

이 태그는 숫자 변수를 각각 더하거나 빼거나 곱하거나 나눌 수 있습니다.

+ give me 5 points
- <add points=5>You have been given 5 points. Your balance is: <get points>.

정의되지 않는 변수 예를 들어 points를 사용하면 기본 0으로 초기화됩니다. 그리고 숫자가 아닐시에는 오류가 발생합니다. 또한 이 태그는 + Trigger와 함께 사용할 수는 없습니다. 

<formal><sentence><uppercase><lowercase>

<formal>은 각 단어의 첫글자를 대문자로 만듭니다.

<sentence>는 첫 단어를 대문자로 만듭니다.

<uppercase><lowercase>전체 문자열을 대문자 또는 소문자로 만듭니다.

  • <call>

이 태그는 매크로를 호출하는 데 사용됩니다.

// Call a macro named "reverse" and give it an argument
+ say * to me in reverse
- <call>reverse <star></call>

다만 이 예제는 우리가 배우지 않은 "Object Macros"에 해당 되는 내용이기 때문에 자세한 내용은 다음에 다루도록 하겠습니다.



'프로그래밍 > RiveScript' 카테고리의 다른 글

[Tutorial] CONCLUSION  (0) 2017.03.09
[Tutorial] TAGS  (0) 2017.03.09
[Tutorial] MORE DEFINITIONS  (0) 2017.03.09
[Tutorial] The Begin Block  (0) 2017.03.09
[Tutorial] Topics  (0) 2017.03.09
[Tutorial] Conditionals  (0) 2017.03.09

begin.rive로 다시 돌아가서 유용한 2가지 타입이 있습니다.


Global Variables


전역 변수(Global Variables)는 봇 변수(bot variables)와 비슷하지만 봇과 관련은 없습니다. 

전역 변수는 RiveScript 인터프리터에 의해 정의 될 수 있습니다. 이번에는  RiveScript 인터프리터에 직접 영향을주는 두 가지 특수한 전역 변수에 대해서 배워 보겠습니다.


Debug Mode

! global debug = true
! global debug = false

이 전역 변수는 RiveScript 인터프리터에서 디버그 모드를 켜거나 끌 수 있습니다. 디버그 모드는 https://play.rivescript.com/에서 Debug Mode를 클릭하면 실제 어떻게 출력 되는지 볼 수 있습니다.


Recursion Depth

! global depth = 50

이 전역 변수는 RiveScript 응답은 서로 리다이렉트 할 수 있기 때문에 무한 재귀를 피할 수있는 보호 기능이 있습니다 (예를 들어, 다른 트리거로 리다이렉트되고 그 트리거가 처음 트리거로 리다이렉트하는 등).

기본적으로 제한 값으로 50으로 설정이 됩니다. 즉, 50개의 리디렉션 후에 응답을 찾을 수 없으면 "ERR: Deep Recursion Detected"라고 응답합니다.  또한 이 값은 0보다 큰수여야 합니다.


Person Substitutions


1인칭 대명사와 2인칭 대명사를 교환하기 위한 기능입니다. brain에서 정의해기 때문에 begin.rive에서 작성 되어야 합니다. 대체를 정의하기 위한 방법은 다음과 같습니다. 

! person i am    = you are
! person you are = i am
! person i'm     = you're
! person you're  = I'm
! person my      = your
! person your    = my
! person you     = I
! person i       = you

대체를 호출 하려면 <person>태그(<star>대치 가능)를 사용하거나 {person}...{/person}로 사용할 수 있습니다. 실제 예를 통해서 테스트를 해보겠습니다.

+ say *
- Umm... "<person>"

실제로 구동 상태에서 "say I am the greatest"라고 메시지를 보내면 봇은 "Umm... "you are the greatest"라고 응답할 것입니다. 만약 대치 하지 않았다고 한다면 봇은 "i am the greatest"라고 말했을 것입니다.

'프로그래밍 > RiveScript' 카테고리의 다른 글

[Tutorial] CONCLUSION  (0) 2017.03.09
[Tutorial] TAGS  (0) 2017.03.09
[Tutorial] MORE DEFINITIONS  (0) 2017.03.09
[Tutorial] The Begin Block  (0) 2017.03.09
[Tutorial] Topics  (0) 2017.03.09
[Tutorial] Conditionals  (0) 2017.03.09

Begin Block은 이전에 저희가 살펴봤던 RiveScript brain의 선택적 기능입니다.

brain에 관한 자세한 내용은 [Tutorial] The Begin File 에서 설명하고 있습니다.

Begin Block또한 begin.rive에 포함되고 토픽과 비슷한 형태로 동작합니다. 예를 들어 보겠습니다.

> begin

  + request
  - {ok}

< begin

Begin Block 응답을 가져오기 위한 전처리와 후처리로 사용됩니다. 

Begin Block이 있으면 사용자가 말하는 각 메시지에 대해 요청 트리거가 작동됩니다.

응답에 {ok}태그가 있으면 사용자 메시지에 대한 응답을 가져오게 됩니다.

> begin

  // If we don't know their name, set the new_user topic and continue.
  + request
  * <get met> == undefined => <set met=true>{topic=new_user}{ok}
  - {ok}

< begin

> topic new_user

  + *
  - Hi! I'm <bot name>! I'm a chatbot written in RiveScript.\s
  ^ What is your name?{topic=asked_name}

< topic

> topic asked_name

  + #
  - Your name is a number?

  + *
  - I only want your first name.

  + _
  - <set name=<formal>>Nice to meet you, <get name>!{topic=random}

< topic

request는 어떤 메시지는 들어 왔을 때 {topic=new_user}로 넘어가게 됩니다. 그리고 이름을 받도록 토픽을 설정해 놨는데 숫자나 특수문자가 아닌 <formal>문자가 들어 왔을 때 이름을 저장하고 토픽 {topic=random}으로 넘어오게 됩니다.


'프로그래밍 > RiveScript' 카테고리의 다른 글

[Tutorial] TAGS  (0) 2017.03.09
[Tutorial] MORE DEFINITIONS  (0) 2017.03.09
[Tutorial] The Begin Block  (0) 2017.03.09
[Tutorial] Topics  (0) 2017.03.09
[Tutorial] Conditionals  (0) 2017.03.09
[Tutorial] Short Discussions  (2) 2017.03.09


LABELED SECTIONS

레이블이 지정된 섹션에는 세 가지 유형이 있습니다.

레이블은 > 기호를 사용하여 정의되며 <로 끝납니다.

완료되면 라벨이 붙은 모든 섹션이 제대로 닫혀 있어야합니다 (파일의 끝에 있더라도).

스타일을 위해서 레이블이 붙은 섹션의 내용도 들여 써야합니다.

라벨이 지정된 섹션은 서로 안에 삽입 할 수 없습니다.


Topics

토픽(Topic)은 트리거의 논리접 그룹을 말합니다. 사용자가 토픽안에 있으면 봇은 토픽 안에 있는 트리거에만 동작합니다.

+ i hate you
- You're really mean! I'm not talking again until you apologize.{topic=sorry}

> topic sorry

  // This will match if the word "sorry" exists ANYWHERE in their message
  + [*] sorry [*]
  - It's OK, I'll forgive you!{topic=random}

  + *
  - Nope, not until you apologize.
  - Say you're sorry!
  - Apologize!

< topic

해당 예제에서는 "i hate you"라고 트리거가 동작할 때 토픽은 sorry로 넘어가며 해당 토픽에서 빠져 나오기 위해서는 sorry라를 트리거를 작동시켜야지만 빠져 나올 수 있습니다. 태그는 가장 기본적으로 정의되는 {topic=random}토픽이며 sorry 토픽에서 빠져나오기 위해서 {topic=random}태그를 사용하게 됩니다.

토픽은 상속 할 수 있으며 다른 트리거를 포함 시킬 수 있습니다. 그렇지만 이것은 고급 사용자를 위한 것이고 Tutorial 범위에 벗어납니다. 이에 대해 자세한 예를 보고 싶다면 rpg.rive를 참고하시기 바랍니다.

존재하지 않는 토픽을 지정하면 더이상 대화를 할 수 없냐고 물어 볼 수 있지만 RiveScript는 이를 감지해서 자동으로 기본 토픽("random")으로 되돌립니다. 

'프로그래밍 > RiveScript' 카테고리의 다른 글

[Tutorial] MORE DEFINITIONS  (0) 2017.03.09
[Tutorial] The Begin Block  (0) 2017.03.09
[Tutorial] Topics  (0) 2017.03.09
[Tutorial] Conditionals  (0) 2017.03.09
[Tutorial] Short Discussions  (2) 2017.03.09
[Tutorial] Redirections  (0) 2017.03.09

Learning Things

우리는 RiveScript의 커맨드에 대해서 배웠습니다. 그런데 봇이 우리 이름도 기억하지 못한다면 이 대화가 무슨 소용일까요?

RiveScript는 사용자에 대한 변수를 저장하고 반복할 수 있는 기능이 있습니다. 사용자의 변수를 저장하기 위해 <set>태그를 사용하고 변수를 검색하기 위해 <get>태그를 사용합니다. 다음 예제를 통해서 우리는 어떻게 사용자 변수를 저장하고 불러 올 수 있는지 알 수 있습니다.

+ my name is *
- <set name=<star>>It's nice to meet you, <get name>.

+ what is my name
- Your name is <get name>, silly!

+ i am # years old
- <set age=<star>>I will remember that you are <get age> years old.

+ how old am i
- You are <get age> years old.

예제를 실행시켜보면 봇은 우리의 나이와 이름을 기억하고 있다는 것을 알 수 있습니다. 또한 변수에 대해서 이야기 하고 있다면 begin.rive에 정의한 변수도 이용 할 수 있지 않을까? 라는 궁금증이 생길 수 있습니다. 유사한 방식으로 변수의 값을 불러 올 수 있습니다. 아래의 예제를 참고하세요. 

! version = 2.0 // Bot variables ! var name = Tutorial ! var age = 5

// The user can ask the bot its name too!
+ what is your name
- You can call me <bot name>.
- My name is <bot name>.

+ how old are you
- I am <bot age> years old.

이제 봇에게 이름과 나이를 물어보면 변수의 저장된 값을 대답할 것입니다.


Writing Conditionals


봇은 사용자의 대한 정보를 학습함으로써, 조건문은 그 변수의 값을 기반으로 응답을 하게 됩니다. 그렇지만 봇에게 내 이름을 말하기전에 내 이름을 물어보게 된다면 "Your name is undefined, silly!" 라고 응답할 것입니다. undefined이라고 나오는 이유는 변수의 값이 저장되어 있지 않기 때문이죠. 이는 봇이 단순 프로그램처럼 느껴질것입니다.

우리는 조건문을 사용하여 봇이 응답하기 전에 내이름을 알고 있는지 모르는지 모른다면 어떻게 응답해야 되는지를 정의 할 수 있습니다. 아래의 예를 보겠습니다.

+ what is my name
* <get name> == undefined => You never told me your name.
- Your name is <get name>, silly!
- Aren't you <get name>?

봇에게 이름을 묻는다면 봇은 정의 되지 않는 내용에 대해서 조건문을 수행할 것입니다. 그리고 이름이 입력 되어 있다면 응답중에서 하나를 보여 줄 것입니다.

조건은 변수를 값과 비교하는데 사용됩니다. 변수를 다른 변수와 비교할 수도 있습니다. 다음 예제를 보겠습니다.

+ my name is *
* <formal>   == <bot name> => Wow, we have the same name!<set name=<formal>>
* <get name> == undefined  => <set name=<formal>>Nice to meet you!
- <set oldname=<get name>><set name=<formal>>
^ I thought your name was <get oldname>?

행이 부족하다면 ^ Continuation커맨드를 사용해서 공간을 확보 할 수 있습니다. 조건은 위에서 아래로 점검됩니다. 

다음과 같은 비교 연산자를 사용할 수 있습니다.

==  equal to
eq  equal to (alias)
!=  not equal to
ne  not equal to (alias)
<>  not equal to (alias)

숫자를 비교하기 위해서는 아래의 비교 연산자를 사용해야 합니다.

<   less than
<=  less than or equal to
>   greater than
>=  greater than or equal to
다음은 다양한 비교 연산자를 사용한 예입니다.
+ what am i old enough to do
* <get age> == undefined => I don't know how old you are.
* <get age> >  25 => You can do anything you want.
* <get age> == 25 => You're old enough to rent a car with no extra fees.
* <get age> >  21 => You're old enough to drink, but not rent a car.
* <get age> == 21 => You're exactly old enough to drink.
* <get age> >  18 => You're old enough to gamble, but not drink.
* <get age> == 18 => You're exactly old enough to gamble.
- You're not old enough to do much of anything yet.


'프로그래밍 > RiveScript' 카테고리의 다른 글

[Tutorial] The Begin Block  (0) 2017.03.09
[Tutorial] Topics  (0) 2017.03.09
[Tutorial] Conditionals  (0) 2017.03.09
[Tutorial] Short Discussions  (2) 2017.03.09
[Tutorial] Redirections  (0) 2017.03.09
[Tutorial] Priority Triggers  (0) 2017.03.08

이야기를 이어나갈때 어떻게 해야 할까요? 다음은 %(Previous)커맨드를 배워보겠습니다.

+ knock knock - who is there? + * % who is there? - <star> who? + * % * who - LOL! <star>! That's funny!

해당 예제를 실행해보면 "knock knock"라고 메시지를 입력했을 때 응답까지는 이해가 될 것입니다. 그다음 %커맨드가 무슨 의미인지를 알아야 되는데. "- who is there?"라는 응답이 왔을 때 그 다음 실행하게 되는 트리거를 정의한 것이라고 볼 수 있습니다. 2번째 트리거는 어떤 단어가 와도 봇은 이것을 가지고 이야기를 이어 나갈 수 있게 됩니다. 다음은 또 다른 예제 입니다.

! array colors = red blue green yellow
+ i have a dog
- What color is it?

+ (@colors)
% what color is it
- That's a silly color for a dog!

예제에서 개를 가지고 있다고 했을때 봇은 어떤 개고 어떤 색인지 물어보고 있습니다. 여기에서 배열에 저장된 칼러를 입력하면 봇은 이어 나갈 수 있지만 엉뚱한 대답을 하게 된다면 봇은 평소와 같은 새 메시지에 반응할 것입니다.

'프로그래밍 > RiveScript' 카테고리의 다른 글

[Tutorial] Topics  (0) 2017.03.09
[Tutorial] Conditionals  (0) 2017.03.09
[Tutorial] Short Discussions  (2) 2017.03.09
[Tutorial] Redirections  (0) 2017.03.09
[Tutorial] Priority Triggers  (0) 2017.03.08
[Tutorial] Arrays in Triggers  (0) 2017.03.08
  1. devel 2018.09.26 16:43 신고

    정말 유용한 정보 감사합니다. 근데 왜 utf-8을 줬음에도 한글로 하면 저 커맨트 가 안 먹힐까요...ㅜㅜ [*] 이것 또한 안먹혀서 애먹고 있습니다......

    • 프리월드 2019.04.05 17:21 신고

      어떤 환경에서 작업을 진행 했는지 모르겠지만 제가 개인적으로 안드로이드 환경에서 돌렸을때는 크게 문제는 없었습니다.

이번 시간에는 리디렉션(Redirections)에 대해서 알아보겠습니다. 

+ hello
- Hi there!
- Hey!
- Howdy!

+ hey
@ hello

+ hi
@ hello

이 예제에서 보기에 "hey", "hi"로 메시지를 보냈을 때 hello라는 트리거가 동작하도록 리디렉션을 정의하고 있습니다. 

+ * or something{weight=100}
- Or something. {@ <star>}

위의 예제를 합쳐서 "hi or something"라고 메시지를 보내면 "Or something. Hey!" 라고 응답할 것입니다. 와일드 카드로 정의된 트리거에 {@ <star>}태그가 리디렉션을 요청합니다. 하나의 와일드 카드만 사용할 경우에는 <@>과 같이 축약해서도 사용 할 수 있습니다.

+ hello *
- {@ hello} <@>

+ hello
- Hi there!

+ are you a bot
- How did you know I'm a machine?

다음 예제를 실행하여 "Hello, are you a bot?" 라고 입력하면 "Hi there! How did you know I'm a machine?"라고 응답할 것입니다.


'프로그래밍 > RiveScript' 카테고리의 다른 글

[Tutorial] Conditionals  (0) 2017.03.09
[Tutorial] Short Discussions  (2) 2017.03.09
[Tutorial] Redirections  (0) 2017.03.09
[Tutorial] Priority Triggers  (0) 2017.03.08
[Tutorial] Arrays in Triggers  (0) 2017.03.08
[Tutorial] Alternatives and Optionals  (0) 2017.03.07

우리는 지난시간까지 트리거에 대한 대부분의 내용을 학습했습니다.

마지막으로 가중치 또는 우선순위 트리거라 불리는 내용에 대해서 배워 보겠습니다. {weight}태그를 가중치 태그라고 이전 시간에 정의했으므로 우선순위 트리거라고 앞으로 설명하겠습니다.

우선순위 트리거는 다른 트리거보다 우선순위가 높습니다.  이는 트리거가 사용자의 메시지와 얼마나 잘 일치 하는지를 임의로 조정하는 데 유용합니다. 다음과 같은 두 가지 트리거가 있다고 가정 해 보겠습니다.

+ google *
- Google search: <a href="http://google.com/search?q=<star>">Click Here</a>

+ * perl script
- You need Perl to run a Perl script.

예제를 실행해서 "google write perl script"라고 메시지를 보내면 우리가 예측하기에는 goolge link를 보여줄 것이라고 생각 할 수 있습니다. 그렇지만 실제를 테스트를 하게 되면 "* perl script" 트리거로 인식해서 응답하는 것을 볼 수 있습니다.

우리는 "+ google *"트리거에 {weight}태그를 추가하여 다른 트리거에 비해 더 중요하게 가중치를 추가 할 수 있습니다. 다음은 트리거에 가중치를 준 예제입니다. 

+ google *{weight=10}
- Google search: <a href="http://google.com/search?q=<star>">Click Here</a>

+ * perl script
- You need Perl to run a Perl script.

기본적으로 가중치를 주지 않은 트리거는 1로 판단됩니다. 그리고 가중치의 값이 높을 수록 더 높은 우선 순위를 가집니다. 0 또는 음수의 값을 가중치로 올 수 없으니 주의 하시기 바랍니다.

'프로그래밍 > RiveScript' 카테고리의 다른 글

[Tutorial] Short Discussions  (2) 2017.03.09
[Tutorial] Redirections  (0) 2017.03.09
[Tutorial] Priority Triggers  (0) 2017.03.08
[Tutorial] Arrays in Triggers  (0) 2017.03.08
[Tutorial] Alternatives and Optionals  (0) 2017.03.07
[Tutorial] TRIGGERS  (0) 2017.03.07

+ Recent posts