<rss
      xmlns:atom="http://www.w3.org/2005/Atom"
      xmlns:media="http://search.yahoo.com/mrss/"
      xmlns:content="http://purl.org/rss/1.0/modules/content/"
      xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
      xmlns:dc="http://purl.org/dc/elements/1.1/"
      version="2.0"
    >
      <channel>
        <title><![CDATA[123🦈ปลาฉลามขึ้นบก]]></title>
        <description><![CDATA[สวัสดีจ้า ทุกท่านที่หลงเข้ามาไม่ว่าจะตั้งใจหรือบังเอิญ แต่ก็หวังว่าจะได้ประโยชน์กับไปไม่มากก็น้อยนะครับ ;)]]></description>
        <link>https://kritta.npub.pro/tag/siamstr/</link>
        <atom:link href="https://kritta.npub.pro/tag/siamstr/rss/" rel="self" type="application/rss+xml"/>
        <itunes:new-feed-url>https://kritta.npub.pro/tag/siamstr/rss/</itunes:new-feed-url>
        <itunes:author><![CDATA[123🦈ปลาฉลามขึ้นบก]]></itunes:author>
        <itunes:subtitle><![CDATA[สวัสดีจ้า ทุกท่านที่หลงเข้ามาไม่ว่าจะตั้งใจหรือบังเอิญ แต่ก็หวังว่าจะได้ประโยชน์กับไปไม่มากก็น้อยนะครับ ;)]]></itunes:subtitle>
        <itunes:type>episodic</itunes:type>
        <itunes:owner>
          <itunes:name><![CDATA[123🦈ปลาฉลามขึ้นบก]]></itunes:name>
          <itunes:email><![CDATA[123🦈ปลาฉลามขึ้นบก]]></itunes:email>
        </itunes:owner>
            
      <pubDate>Thu, 02 May 2024 06:49:46 GMT</pubDate>
      <lastBuildDate>Thu, 02 May 2024 06:49:46 GMT</lastBuildDate>
      
      <itunes:image href="https://image.nostr.build/28545859b808c06c6a03f4119432c684b68c3d3205a4bfdf74b66e2a55b62dac.gif" />
      <image>
        <title><![CDATA[123🦈ปลาฉลามขึ้นบก]]></title>
        <link>https://kritta.npub.pro/tag/siamstr/</link>
        <url>https://image.nostr.build/28545859b808c06c6a03f4119432c684b68c3d3205a4bfdf74b66e2a55b62dac.gif</url>
      </image>
      <item>
      <title><![CDATA[ห้องทดลองของหลาม #1]]></title>
      <description><![CDATA[]]></description>
             <itunes:subtitle><![CDATA[]]></itunes:subtitle>
      <pubDate>Thu, 02 May 2024 06:49:46 GMT</pubDate>
      <link>https://kritta.npub.pro/post/rbucw9lxpcmxdmymiuxtq/</link>
      <comments>https://kritta.npub.pro/post/rbucw9lxpcmxdmymiuxtq/</comments>
      <guid isPermaLink="false">naddr1qq2hycn4gdmnjmrc2pp567ryf4u566t4tp29zq3qvm0kq43djwdd4psjgdjgn9z6fm836c35dv7eg7x74z3n3ueq83jqxpqqqp65wptn7f8</guid>
      <category>หลามมันร้าย</category>
      
        <media:content url="https://yakihonne.s3.ap-east-1.amazonaws.com/66df60562d939ada8612436489945a4ecf1d62346b3d9478dea8a338f3203c64/files/1714632586167-YAKIHONNES3.jpg" medium="image"/>
        <enclosure 
          url="https://yakihonne.s3.ap-east-1.amazonaws.com/66df60562d939ada8612436489945a4ecf1d62346b3d9478dea8a338f3203c64/files/1714632586167-YAKIHONNES3.jpg" length="0" 
          type="image/jpeg" 
        />
      <noteId>naddr1qq2hycn4gdmnjmrc2pp567ryf4u566t4tp29zq3qvm0kq43djwdd4psjgdjgn9z6fm836c35dv7eg7x74z3n3ueq83jqxpqqqp65wptn7f8</noteId>
      <npub>npub1vm0kq43djwdd4psjgdjgn9z6fm836c35dv7eg7x74z3n3ueq83jqhkxp8e</npub>
      <dc:creator><![CDATA[123🦈ปลาฉลามขึ้นบก]]></dc:creator>
      <content:encoded><![CDATA[<h2>สร้าง Nostr Bot ด้วย Python</h2>
<blockquote>
<p><em>อะแฮ่ม ขอชี้แจงไว้ก่อนว่า ผมเขียนเพื่อให้ตัวเองอ่านเพื่อวันไหนจะกลับมาทำต่อจะได้พอจำได้ว่าตัวเองทำอะไรลงไปบ้าง เพราะงั้นบางส่วนในบทความนี้อาจจะไม่ละเอียด หากลองทำตามแล้วติดตรงไหนอยากสอบถาม ติดต่อมาได้ที่ Nostr Address: <a href="https://nosta.me/nprofile1qy0hwumn8ghj7mn0wd68ytfsxvhxgmmjv9nxzcm5dae8jtn0wfnj7qgcwaehxw309aex2mrp0yhxumm5daeks6fwwa5kutcqypnd7czk9kfe4k5xzfpkfzv5tf8v78tzx34nm9rcm652xw8nyq7xgdzhmy5">kritta@rightshift.to</a> หรือลองค้นเพิ่มเติมใน link ท้ายบทความนะครับ</em></p>
</blockquote>
<p>อย่างที่หลาย ๆ คนน่าจะทราบกันดีอยู่แล้วว่า Nostr เป็น open protocol ที่ใคร ๆ ก็สามารถเข้ามามีส่วนร่วมในการพัฒนาได้ ทำให้มีโปรเจคต่าง ๆ เกิดขึ้นมากมาย โดยบทความในชุดนี้ผมจะหยิบโปรเจคต่าง ๆ ที่น่าสนใจมามาลองเล่น และนำมาเล่าสู่กันฟัง หวังว่าผู้ที่หลงเข้ามาอ่านจะได้ประโยชน์จากสิ่งนี้นะครับ ;)</p>
<p>โดยในวันนี้โปรเจคที่ผมหยิบมาคือ NDK (Nostr development kit) ผมหาไม่เจอว่าใครเป็นคนเริ่มโปรเจค แต่คนที่ดูแล repo นี้หลัก ๆ คือคุณ <a href="https://nosta.me/nprofile1qywhwumn8ghj7mn0wd68ytndw46xjmnewaskcmr9wshxxmmd9uq36amnwvaz7tmjv4kxz7fwd46hg6tw09mkzmrvv46zucm0d5hsqgrgmqgktyvpqzma5slu9rmarlqj24zxdcg3tzrtneamxfhktmzzwg2x3v53">yukibtc</a> เริ่มต้นเหมือนจะเริ่มจาก RUST แต่ตอนนี้เหมือนจะแตกไป swift, java, python เอาจริง ๆ ผมไม่รู้หรอกว่าใครเป็นคนทำภาษาไหนเพราะ contributors เขาเยอะมาก แต่ก็นั่นแหละ ขอบคุณที่สร้างอะไรสนุก ๆ แบบนี้ออกมาให้ได้เล่นนะครับ</p>
<p>โดยอย่างแรกที่เราต้องเริ่มคือการสั่ง pip install ตัว nostr sdk เพื่อใช่งาน สำหรับคนที่ไม่มี python ในเครื่องก็ไปลง python ก่อนด้วยนะ หรือจะใช้ online ผ่าน google colab ลองเล่นดูก่อนก็ได้</p>
<pre><code>pip install pip install nostr-sdk 
</code></pre>
<p>จากนั้นเราก็จะสามารถใช้งาน Nostr_sdk ได้แล้ว!!!<br>โดยการที่เราจะเข้ามาใช้งาน Nostr ได้นั้นเราจำเป็นต้องมี keys เพื่อเข้าสู่ระบบเสียก่อนงั้นเรามาเริ่มจากการสร้าง keys กันก่อน</p>
<pre><code>from nostr_sdk import  Keys
#เพียงคำสั่งนี้คำสั่งเดียวก็ได้ keys แล้วงั้นเหรอ!!
keys = Keys.generate()
#แยก keys ออกเป็น secret key (sk) และ public key (pk) 
sk = keys.secret_key()
pk = keys.public_key()
#ไหน ๆ ขอดู keys หน่อยสิ้
print(f"public key: {pk.to_bech32()}")
print(f"Secret key: {sk.to_bech32()}")
#output:
#public key: npub1wkxaxzmmamc6h8n6ev7yq3y5qmqnyxmu0xmrllcepxup9tktuzrsu646r0
#Secret key: nsec160gefyqkderqlnr545ps4d5th6pex3ducqgcev69z0rstqakkv9scvat97
</code></pre>
<p><em>note ถ้าสร้าง keys เสร็จแล้วเอาไปเก็บไว้ในพวก dot env จะปลอดภัยและสะดวกในการใช้ต่อมากกว่า</em></p>
<p>แล้วหลังจากได้ keys มาแล้วเราจำเป็นต้องกำหนด signer, client และ relay ที่เราจะใช้ในการรับ event ของเรา</p>
<pre><code># กำหนด keys ที่เราพึ่งสร้างให้เป็นตัว sign event
signer = NostrSigner.keys(keys)
# นำเข้า key ที่มีอยู่แล้ว
# app_keys = Keys.parse("nsec......")
# signer = NostrSigner.keys(app_keys)
# หรือใช้ NIP46 signer
# uri = NostrConnectUri.parse("bunker://.. or nostrconnect://..")
# nip46 = Nip46Signer(uri, app_keys, timedelta(seconds=60), None)
# signer = NostrSigner.nip46(nip46)
# กำหนด client ให้ใช้ signer ตัวนี้ (feel like log in)
client = Client(signer)

# เพิ่ม relays ที่จะเก็บ event
client.add_relays(["wss://relay.damus.io", "wss://siamstr.com", "wss://siamstr.com","wss://relay.notoshi.win"])
client.connect()
#ตั้งชื่อให้ account เราสักหน่อยเพื่อเช็คด้วยว่า เราเชื่อมต่อ relay ต่าง ๆ ผ่านมั้ย
client.set_metadata(Metadata().set_name("Testing หลาม ๆ"))
</code></pre>
<p>หลังจากกำหนดทุกอย่างเรียบร้อยแล้ว เรามาลองสร้างโพสต์แรกกันเลยดีกว่า</p>
<pre><code>#tag เพื่อเอาไว้เติมส่วนต่าง ๆ นอกจาก เนื้อหาของโน๊ต เช่นการ mention การใส่ hashtag
# p = mention
# t = hashtag
tag = Tag.parse(["p", "66df60562d939ada8612436489945a4ecf1d62346b3d9478dea8a338f3203c64"])
#ใส่เนื้อหาที่เราค้องการโพสต์
builder = EventBuilder.text_note("สวัสดีชาวทุ่ง ", [tag])
ส่ง event ไปให้ relay โลดดดด
client.send_event_builder(builder)
</code></pre>
<p>แล้วนอกจากโพสต์ตระกูล kind:1 แล้วเรายังโพสต์ kind อื่น ๆ ได้ด้วย</p>
<pre><code># ส่งจ้อความส่วนตัว
receiver_pk = PublicKey.from_bech32("npubคนรับ")
event = EventBuilder.encrypted_direct_msg(keys, receiver_pk, "ข้อความ", None).to_event(keys)
print(event.as_json())

# templateเปล่า
kind = Kind(เลข kind)
content = "..."
tags = []
builder = EventBuilder(kind, content, tags)


# POW
event = builder.to_pow_event(keys, 20)
print(f"POW event: {event.as_json()}")
</code></pre>
<p>ส่วนตัวผมมองว่าส่วนนี้แหละคือส่วนที่สนุกที่สุดของวันนี้ เพราะเป็นจุดที่เราสามารถนำมันออกไปต่อยอดได้มากที่สุด เช่นการเชื่อมต่อกับ service อื่น ๆ เช่น mempool.space เพื่อส่งค่าฟี bitcoin ให้เราผ่านแชท, ทำเกมง่าย ๆ เล่นกับเพื่อน ๆ หน้า timeline อย่าง cowdle หรือ หวย อย่างที่เห็นกันไปในช่วงก่อนหน้านี้ หรือใช้ทำงานกรรมกรแทนเรา เช่นการแจก badges ที่ทาง rightshift ได้ทำไปก่อนหน้า, bot relay notoshi, zapbot และอีกต่าง ๆ มากมาย</p>
<h3>filter</h3>
<p>ตัว filter เป็นคำสั่งที่ช่วยเรากรอง event ที่จะขอจาก relay ใช้เพื่อรับเฉพาะ event ที่เราต้องการเท่านั้น</p>
<pre><code>
f = (Filter()
     .pubkey(keys.public_key())
     .kinds([Kind(0), Kind.from_enum(KindEnum.TEXT_NOTE())])
     .custom_tag(SingleLetterTag.lowercase(Alphabet.J), ["test"])
     )
print(f.as_json())
# output: {"kinds":[0,1],"#j":["test"],"#p":["758dd30b7beef1ab9e7acb3c40449406c1321b7c79b63fff1909b812aecbe087"]}

f = f.kind(Kind(4)).custom_tag(SingleLetterTag.lowercase(Alphabet.J), ["append-new"])
print(f.as_json())
{"kinds":[0,1,4],"#j":["test","append-new"],"#p":["758dd30b7beef1ab9e7acb3c40449406c1321b7c79b63fff1909b812aecbe087"]}

#ตัวอย่างเช่นรับเฉพาะ event ของคนที่ใช้ notoshi relay
filter =Filter().kind(Kind(10002)).custom_tag(SingleLetterTag.lowercase(Alphabet.R), ["wss://relay.notoshi.win"])
events = client.get_events_of([filter], timedelta(seconds=30))
</code></pre>
<p>สองฟังก์ชันนี้เป็นตัวสำคัญในการทำบอทในส่วนต่อไปจะเป็นตัวเสริมต่าง ๆ ที่เพิ่มลูกเล่นให้บอทได้</p>
<h3>Metadata</h3>
<p>metadata มีไว้แก้ไขข้อมูลต่าง ๆ ในโปรไฟล์ของเรา</p>
<pre><code>metadata = Metadata().set_name("username")\
        .set_display_name("My Username")\
        .set_about("Description")\
        .set_picture("https://example.com/avatar.png")\
        .set_banner("https://example.com/banner.png")\
        .set_nip05("username@example.com")\
        .set_lud16("username@example.com")
# name = ชื้อผู้ใช่
# display_name = ชื่อที่จะแสดงให้คนอื่นเห็น (ถ้าช่องนี้ว่างมักจะโชว์ชื่อที่ใว่ในช่อง name)
# about = bio
# picture = รูปโปรไฟล์
# banner = รูปปก
# nip05 = Nostr addr
# lud16 = Lightning addr
</code></pre>
<h3>NWC</h3>
<p>NWC หรือ Nostr wallet connection มีไว้ใช้ในการเชื่อมต่อกับกระเป๋า ln ของเราเพื่อคุมกระเป๋าของเราผ่าน Nostr</p>
<pre><code># นำ NWC uri มาวาง
uri = NostrWalletConnectUri.parse("nostr+walletconnect://..")

# สร้าง client ในรูปแบบที่เพิ่มการ zap
keys = Keys.generate()
signer = NostrSigner.keys(keys)
zapper = NostrZapper.nwc(uri)
client = ClientBuilder().signer(signer).zapper(zapper).build()

client.add_relay("wss://relay.damus.io")
client.connect()

pk = PublicKey.from_bech32(" npub คนรับ")
client.zap(ZapEntity.public_key(pk), 1000, None)
</code></pre>
<h2>Bot template</h2>
<pre><code>
from nostr_sdk import Client, NostrSigner, Keys, Event, UnsignedEvent, Filter, \
    HandleNotification, Timestamp, nip04_decrypt, UnwrappedGift, init_logger, LogLevel, Kind, KindEnum
import time

init_logger(LogLevel.DEBUG)

# sk = SecretKey.from_bech32("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")
# keys = Keys(sk)
# OR
keys = Keys.parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")

sk = keys.secret_key()
pk = keys.public_key()
print(f"Bot public key: {pk.to_bech32()}")

signer = NostrSigner.keys(keys)
client = Client(signer)

client.add_relay("wss://relay.damus.io")
client.add_relay("wss://nostr.mom")
client.add_relay("wss://nostr.oxtr.dev")
client.connect()

now = Timestamp.now()

nip04_filter = Filter().pubkey(pk).kind(Kind.from_enum(KindEnum.ENCRYPTED_DIRECT_MESSAGE())).since(now)
nip59_filter = Filter().pubkey(pk).kind(Kind.from_enum(KindEnum.GIFT_WRAP())).since(
    Timestamp.from_secs(now.as_secs() - 60 * 60 * 24 * 7))  # NIP59 have a tweaked timestamp (in the past)
client.subscribe([nip04_filter, nip59_filter], None)


class NotificationHandler(HandleNotification):
    def handle(self, relay_url, subscription_id, event: Event):
        print(f"Received new event from {relay_url}: {event.as_json()}")
        if event.kind().match_enum(KindEnum.ENCRYPTED_DIRECT_MESSAGE()):
            print("Decrypting NIP04 event")
            try:
                msg = nip04_decrypt(sk, event.author(), event.content())
                print(f"Received new msg: {msg}")
                client.send_direct_msg(event.author(), f"Echo: {msg}", event.id())
            except Exception as e:
                print(f"Error during content NIP04 decryption: {e}")
        elif event.kind().match_enum(KindEnum.GIFT_WRAP()):
            print("Decrypting NIP59 event")
            try:
                # Extract rumor
                unwrapped_gift = UnwrappedGift.from_gift_wrap(keys, event)
                sender = unwrapped_gift.sender()
                rumor: UnsignedEvent = unwrapped_gift.rumor()

                # Check timestamp of rumor
                if rumor.created_at().as_secs() &gt;= now.as_secs():
                    if rumor.kind().match_enum(KindEnum.SEALED_DIRECT()):
                        msg = rumor.content()
                        print(f"Received new msg [sealed]: {msg}")
                        client.send_sealed_msg(sender, f"Echo: {msg}", None)
                    else:
                        print(f"{rumor.as_json()}")
            except Exception as e:
                print(f"Error during content NIP59 decryption: {e}")

    def handle_msg(self, relay_url, msg):
        None


abortable = client.handle_notifications(NotificationHandler())
# Optionally, to abort handle notifications look, call abortable.abort()

while True:
    time.sleep(5.0)
    # abortable.abort()
</code></pre>
<p>ผมหวังว่าบทความนี้จะมีประโยชน์กับคนอ่าน และคาดหวังที่จะได้เห็น service ต่าง ๆ ที่สร้างสรรค์เกิดขึ้นหลังจากนี้<br>Link เพิ่มเติมที่สำหรับศึกษาต่อ</p>
<ul>
<li><np-embed url="https://github.com/rust-nostr/nostr/tree/master"><a href="https://github.com/rust-nostr/nostr/tree/master">https://github.com/rust-nostr/nostr/tree/master</a></np-embed></li>
<li><np-embed url="https://github.com/nostr-protocol/nips"><a href="https://github.com/nostr-protocol/nips">https://github.com/nostr-protocol/nips</a></np-embed></li>
</ul>
]]></content:encoded>
      <itunes:author><![CDATA[123🦈ปลาฉลามขึ้นบก]]></itunes:author>
      <itunes:summary><![CDATA[<h2>สร้าง Nostr Bot ด้วย Python</h2>
<blockquote>
<p><em>อะแฮ่ม ขอชี้แจงไว้ก่อนว่า ผมเขียนเพื่อให้ตัวเองอ่านเพื่อวันไหนจะกลับมาทำต่อจะได้พอจำได้ว่าตัวเองทำอะไรลงไปบ้าง เพราะงั้นบางส่วนในบทความนี้อาจจะไม่ละเอียด หากลองทำตามแล้วติดตรงไหนอยากสอบถาม ติดต่อมาได้ที่ Nostr Address: <a href="https://nosta.me/nprofile1qy0hwumn8ghj7mn0wd68ytfsxvhxgmmjv9nxzcm5dae8jtn0wfnj7qgcwaehxw309aex2mrp0yhxumm5daeks6fwwa5kutcqypnd7czk9kfe4k5xzfpkfzv5tf8v78tzx34nm9rcm652xw8nyq7xgdzhmy5">kritta@rightshift.to</a> หรือลองค้นเพิ่มเติมใน link ท้ายบทความนะครับ</em></p>
</blockquote>
<p>อย่างที่หลาย ๆ คนน่าจะทราบกันดีอยู่แล้วว่า Nostr เป็น open protocol ที่ใคร ๆ ก็สามารถเข้ามามีส่วนร่วมในการพัฒนาได้ ทำให้มีโปรเจคต่าง ๆ เกิดขึ้นมากมาย โดยบทความในชุดนี้ผมจะหยิบโปรเจคต่าง ๆ ที่น่าสนใจมามาลองเล่น และนำมาเล่าสู่กันฟัง หวังว่าผู้ที่หลงเข้ามาอ่านจะได้ประโยชน์จากสิ่งนี้นะครับ ;)</p>
<p>โดยในวันนี้โปรเจคที่ผมหยิบมาคือ NDK (Nostr development kit) ผมหาไม่เจอว่าใครเป็นคนเริ่มโปรเจค แต่คนที่ดูแล repo นี้หลัก ๆ คือคุณ <a href="https://nosta.me/nprofile1qywhwumn8ghj7mn0wd68ytndw46xjmnewaskcmr9wshxxmmd9uq36amnwvaz7tmjv4kxz7fwd46hg6tw09mkzmrvv46zucm0d5hsqgrgmqgktyvpqzma5slu9rmarlqj24zxdcg3tzrtneamxfhktmzzwg2x3v53">yukibtc</a> เริ่มต้นเหมือนจะเริ่มจาก RUST แต่ตอนนี้เหมือนจะแตกไป swift, java, python เอาจริง ๆ ผมไม่รู้หรอกว่าใครเป็นคนทำภาษาไหนเพราะ contributors เขาเยอะมาก แต่ก็นั่นแหละ ขอบคุณที่สร้างอะไรสนุก ๆ แบบนี้ออกมาให้ได้เล่นนะครับ</p>
<p>โดยอย่างแรกที่เราต้องเริ่มคือการสั่ง pip install ตัว nostr sdk เพื่อใช่งาน สำหรับคนที่ไม่มี python ในเครื่องก็ไปลง python ก่อนด้วยนะ หรือจะใช้ online ผ่าน google colab ลองเล่นดูก่อนก็ได้</p>
<pre><code>pip install pip install nostr-sdk 
</code></pre>
<p>จากนั้นเราก็จะสามารถใช้งาน Nostr_sdk ได้แล้ว!!!<br>โดยการที่เราจะเข้ามาใช้งาน Nostr ได้นั้นเราจำเป็นต้องมี keys เพื่อเข้าสู่ระบบเสียก่อนงั้นเรามาเริ่มจากการสร้าง keys กันก่อน</p>
<pre><code>from nostr_sdk import  Keys
#เพียงคำสั่งนี้คำสั่งเดียวก็ได้ keys แล้วงั้นเหรอ!!
keys = Keys.generate()
#แยก keys ออกเป็น secret key (sk) และ public key (pk) 
sk = keys.secret_key()
pk = keys.public_key()
#ไหน ๆ ขอดู keys หน่อยสิ้
print(f"public key: {pk.to_bech32()}")
print(f"Secret key: {sk.to_bech32()}")
#output:
#public key: npub1wkxaxzmmamc6h8n6ev7yq3y5qmqnyxmu0xmrllcepxup9tktuzrsu646r0
#Secret key: nsec160gefyqkderqlnr545ps4d5th6pex3ducqgcev69z0rstqakkv9scvat97
</code></pre>
<p><em>note ถ้าสร้าง keys เสร็จแล้วเอาไปเก็บไว้ในพวก dot env จะปลอดภัยและสะดวกในการใช้ต่อมากกว่า</em></p>
<p>แล้วหลังจากได้ keys มาแล้วเราจำเป็นต้องกำหนด signer, client และ relay ที่เราจะใช้ในการรับ event ของเรา</p>
<pre><code># กำหนด keys ที่เราพึ่งสร้างให้เป็นตัว sign event
signer = NostrSigner.keys(keys)
# นำเข้า key ที่มีอยู่แล้ว
# app_keys = Keys.parse("nsec......")
# signer = NostrSigner.keys(app_keys)
# หรือใช้ NIP46 signer
# uri = NostrConnectUri.parse("bunker://.. or nostrconnect://..")
# nip46 = Nip46Signer(uri, app_keys, timedelta(seconds=60), None)
# signer = NostrSigner.nip46(nip46)
# กำหนด client ให้ใช้ signer ตัวนี้ (feel like log in)
client = Client(signer)

# เพิ่ม relays ที่จะเก็บ event
client.add_relays(["wss://relay.damus.io", "wss://siamstr.com", "wss://siamstr.com","wss://relay.notoshi.win"])
client.connect()
#ตั้งชื่อให้ account เราสักหน่อยเพื่อเช็คด้วยว่า เราเชื่อมต่อ relay ต่าง ๆ ผ่านมั้ย
client.set_metadata(Metadata().set_name("Testing หลาม ๆ"))
</code></pre>
<p>หลังจากกำหนดทุกอย่างเรียบร้อยแล้ว เรามาลองสร้างโพสต์แรกกันเลยดีกว่า</p>
<pre><code>#tag เพื่อเอาไว้เติมส่วนต่าง ๆ นอกจาก เนื้อหาของโน๊ต เช่นการ mention การใส่ hashtag
# p = mention
# t = hashtag
tag = Tag.parse(["p", "66df60562d939ada8612436489945a4ecf1d62346b3d9478dea8a338f3203c64"])
#ใส่เนื้อหาที่เราค้องการโพสต์
builder = EventBuilder.text_note("สวัสดีชาวทุ่ง ", [tag])
ส่ง event ไปให้ relay โลดดดด
client.send_event_builder(builder)
</code></pre>
<p>แล้วนอกจากโพสต์ตระกูล kind:1 แล้วเรายังโพสต์ kind อื่น ๆ ได้ด้วย</p>
<pre><code># ส่งจ้อความส่วนตัว
receiver_pk = PublicKey.from_bech32("npubคนรับ")
event = EventBuilder.encrypted_direct_msg(keys, receiver_pk, "ข้อความ", None).to_event(keys)
print(event.as_json())

# templateเปล่า
kind = Kind(เลข kind)
content = "..."
tags = []
builder = EventBuilder(kind, content, tags)


# POW
event = builder.to_pow_event(keys, 20)
print(f"POW event: {event.as_json()}")
</code></pre>
<p>ส่วนตัวผมมองว่าส่วนนี้แหละคือส่วนที่สนุกที่สุดของวันนี้ เพราะเป็นจุดที่เราสามารถนำมันออกไปต่อยอดได้มากที่สุด เช่นการเชื่อมต่อกับ service อื่น ๆ เช่น mempool.space เพื่อส่งค่าฟี bitcoin ให้เราผ่านแชท, ทำเกมง่าย ๆ เล่นกับเพื่อน ๆ หน้า timeline อย่าง cowdle หรือ หวย อย่างที่เห็นกันไปในช่วงก่อนหน้านี้ หรือใช้ทำงานกรรมกรแทนเรา เช่นการแจก badges ที่ทาง rightshift ได้ทำไปก่อนหน้า, bot relay notoshi, zapbot และอีกต่าง ๆ มากมาย</p>
<h3>filter</h3>
<p>ตัว filter เป็นคำสั่งที่ช่วยเรากรอง event ที่จะขอจาก relay ใช้เพื่อรับเฉพาะ event ที่เราต้องการเท่านั้น</p>
<pre><code>
f = (Filter()
     .pubkey(keys.public_key())
     .kinds([Kind(0), Kind.from_enum(KindEnum.TEXT_NOTE())])
     .custom_tag(SingleLetterTag.lowercase(Alphabet.J), ["test"])
     )
print(f.as_json())
# output: {"kinds":[0,1],"#j":["test"],"#p":["758dd30b7beef1ab9e7acb3c40449406c1321b7c79b63fff1909b812aecbe087"]}

f = f.kind(Kind(4)).custom_tag(SingleLetterTag.lowercase(Alphabet.J), ["append-new"])
print(f.as_json())
{"kinds":[0,1,4],"#j":["test","append-new"],"#p":["758dd30b7beef1ab9e7acb3c40449406c1321b7c79b63fff1909b812aecbe087"]}

#ตัวอย่างเช่นรับเฉพาะ event ของคนที่ใช้ notoshi relay
filter =Filter().kind(Kind(10002)).custom_tag(SingleLetterTag.lowercase(Alphabet.R), ["wss://relay.notoshi.win"])
events = client.get_events_of([filter], timedelta(seconds=30))
</code></pre>
<p>สองฟังก์ชันนี้เป็นตัวสำคัญในการทำบอทในส่วนต่อไปจะเป็นตัวเสริมต่าง ๆ ที่เพิ่มลูกเล่นให้บอทได้</p>
<h3>Metadata</h3>
<p>metadata มีไว้แก้ไขข้อมูลต่าง ๆ ในโปรไฟล์ของเรา</p>
<pre><code>metadata = Metadata().set_name("username")\
        .set_display_name("My Username")\
        .set_about("Description")\
        .set_picture("https://example.com/avatar.png")\
        .set_banner("https://example.com/banner.png")\
        .set_nip05("username@example.com")\
        .set_lud16("username@example.com")
# name = ชื้อผู้ใช่
# display_name = ชื่อที่จะแสดงให้คนอื่นเห็น (ถ้าช่องนี้ว่างมักจะโชว์ชื่อที่ใว่ในช่อง name)
# about = bio
# picture = รูปโปรไฟล์
# banner = รูปปก
# nip05 = Nostr addr
# lud16 = Lightning addr
</code></pre>
<h3>NWC</h3>
<p>NWC หรือ Nostr wallet connection มีไว้ใช้ในการเชื่อมต่อกับกระเป๋า ln ของเราเพื่อคุมกระเป๋าของเราผ่าน Nostr</p>
<pre><code># นำ NWC uri มาวาง
uri = NostrWalletConnectUri.parse("nostr+walletconnect://..")

# สร้าง client ในรูปแบบที่เพิ่มการ zap
keys = Keys.generate()
signer = NostrSigner.keys(keys)
zapper = NostrZapper.nwc(uri)
client = ClientBuilder().signer(signer).zapper(zapper).build()

client.add_relay("wss://relay.damus.io")
client.connect()

pk = PublicKey.from_bech32(" npub คนรับ")
client.zap(ZapEntity.public_key(pk), 1000, None)
</code></pre>
<h2>Bot template</h2>
<pre><code>
from nostr_sdk import Client, NostrSigner, Keys, Event, UnsignedEvent, Filter, \
    HandleNotification, Timestamp, nip04_decrypt, UnwrappedGift, init_logger, LogLevel, Kind, KindEnum
import time

init_logger(LogLevel.DEBUG)

# sk = SecretKey.from_bech32("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")
# keys = Keys(sk)
# OR
keys = Keys.parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")

sk = keys.secret_key()
pk = keys.public_key()
print(f"Bot public key: {pk.to_bech32()}")

signer = NostrSigner.keys(keys)
client = Client(signer)

client.add_relay("wss://relay.damus.io")
client.add_relay("wss://nostr.mom")
client.add_relay("wss://nostr.oxtr.dev")
client.connect()

now = Timestamp.now()

nip04_filter = Filter().pubkey(pk).kind(Kind.from_enum(KindEnum.ENCRYPTED_DIRECT_MESSAGE())).since(now)
nip59_filter = Filter().pubkey(pk).kind(Kind.from_enum(KindEnum.GIFT_WRAP())).since(
    Timestamp.from_secs(now.as_secs() - 60 * 60 * 24 * 7))  # NIP59 have a tweaked timestamp (in the past)
client.subscribe([nip04_filter, nip59_filter], None)


class NotificationHandler(HandleNotification):
    def handle(self, relay_url, subscription_id, event: Event):
        print(f"Received new event from {relay_url}: {event.as_json()}")
        if event.kind().match_enum(KindEnum.ENCRYPTED_DIRECT_MESSAGE()):
            print("Decrypting NIP04 event")
            try:
                msg = nip04_decrypt(sk, event.author(), event.content())
                print(f"Received new msg: {msg}")
                client.send_direct_msg(event.author(), f"Echo: {msg}", event.id())
            except Exception as e:
                print(f"Error during content NIP04 decryption: {e}")
        elif event.kind().match_enum(KindEnum.GIFT_WRAP()):
            print("Decrypting NIP59 event")
            try:
                # Extract rumor
                unwrapped_gift = UnwrappedGift.from_gift_wrap(keys, event)
                sender = unwrapped_gift.sender()
                rumor: UnsignedEvent = unwrapped_gift.rumor()

                # Check timestamp of rumor
                if rumor.created_at().as_secs() &gt;= now.as_secs():
                    if rumor.kind().match_enum(KindEnum.SEALED_DIRECT()):
                        msg = rumor.content()
                        print(f"Received new msg [sealed]: {msg}")
                        client.send_sealed_msg(sender, f"Echo: {msg}", None)
                    else:
                        print(f"{rumor.as_json()}")
            except Exception as e:
                print(f"Error during content NIP59 decryption: {e}")

    def handle_msg(self, relay_url, msg):
        None


abortable = client.handle_notifications(NotificationHandler())
# Optionally, to abort handle notifications look, call abortable.abort()

while True:
    time.sleep(5.0)
    # abortable.abort()
</code></pre>
<p>ผมหวังว่าบทความนี้จะมีประโยชน์กับคนอ่าน และคาดหวังที่จะได้เห็น service ต่าง ๆ ที่สร้างสรรค์เกิดขึ้นหลังจากนี้<br>Link เพิ่มเติมที่สำหรับศึกษาต่อ</p>
<ul>
<li><np-embed url="https://github.com/rust-nostr/nostr/tree/master"><a href="https://github.com/rust-nostr/nostr/tree/master">https://github.com/rust-nostr/nostr/tree/master</a></np-embed></li>
<li><np-embed url="https://github.com/nostr-protocol/nips"><a href="https://github.com/nostr-protocol/nips">https://github.com/nostr-protocol/nips</a></np-embed></li>
</ul>
]]></itunes:summary>
      <itunes:image href="https://yakihonne.s3.ap-east-1.amazonaws.com/66df60562d939ada8612436489945a4ecf1d62346b3d9478dea8a338f3203c64/files/1714632586167-YAKIHONNES3.jpg"/>
      </item>
      
      <item>
      <title><![CDATA[[REVIEW] Primal, freeform On IOS]]></title>
      <description><![CDATA[ในบทความนี้เป็นการรีวิวการใช้ client ต่าง ๆ บน IOS ของผมและเพื่อน ๆ ที่ช่วยกันลองเล่นและทดสอบเพื่อหา client บน IOS ที่จะใช้ป้ายยาคนใหม่ ๆ ให้เขาไม่รู้สึกว่าการเข้ามาในทุ่งม่วงแห่งนี้เป็นเรื่องยาก เมื่อได้ทดลองแล้วเลยอยากมาแชร์ผลที่ได้กับทุก ๆ คนบน Nostr เพื่อเป็นแนวทางหรือเป็นส่วนประกอบการตัดสินใจในการเข้าไปลองเล่น client ใหม่ ๆ]]></description>
             <itunes:subtitle><![CDATA[ในบทความนี้เป็นการรีวิวการใช้ client ต่าง ๆ บน IOS ของผมและเพื่อน ๆ ที่ช่วยกันลองเล่นและทดสอบเพื่อหา client บน IOS ที่จะใช้ป้ายยาคนใหม่ ๆ ให้เขาไม่รู้สึกว่าการเข้ามาในทุ่งม่วงแห่งนี้เป็นเรื่องยาก เมื่อได้ทดลองแล้วเลยอยากมาแชร์ผลที่ได้กับทุก ๆ คนบน Nostr เพื่อเป็นแนวทางหรือเป็นส่วนประกอบการตัดสินใจในการเข้าไปลองเล่น client ใหม่ ๆ]]></itunes:subtitle>
      <pubDate>Mon, 23 Oct 2023 11:07:41 GMT</pubDate>
      <link>https://kritta.npub.pro/post/review_client1/</link>
      <comments>https://kritta.npub.pro/post/review_client1/</comments>
      <guid isPermaLink="false">naddr1qq88yetkd9jhwhmrd35k2mn5xypzqeklvptzmyu6m2rpysmy3x295nk0r43rg6eaj3uda29r8rejq0ryqvzqqqr4guemky46</guid>
      <category>nostr</category>
      
        <media:content url="https://cdn.discordapp.com/attachments/1120171425433661440/1165969352005259365/a-purple-ostrich-in-a-meadow-full-of-fun-and-adven-upscaled.png?ex=6548c894&amp;is=65365394&amp;hm=674b41cb27807180a549500c7d5c29a4ab4d2c7e68ae3aee7b818aaa693159f5&amp;" medium="image"/>
        <enclosure 
          url="https://cdn.discordapp.com/attachments/1120171425433661440/1165969352005259365/a-purple-ostrich-in-a-meadow-full-of-fun-and-adven-upscaled.png?ex=6548c894&amp;is=65365394&amp;hm=674b41cb27807180a549500c7d5c29a4ab4d2c7e68ae3aee7b818aaa693159f5&amp;" length="0" 
          type="image/png" 
        />
      <noteId>naddr1qq88yetkd9jhwhmrd35k2mn5xypzqeklvptzmyu6m2rpysmy3x295nk0r43rg6eaj3uda29r8rejq0ryqvzqqqr4guemky46</noteId>
      <npub>npub1vm0kq43djwdd4psjgdjgn9z6fm836c35dv7eg7x74z3n3ueq83jqhkxp8e</npub>
      <dc:creator><![CDATA[123🦈ปลาฉลามขึ้นบก]]></dc:creator>
      <content:encoded><![CDATA[<h2>[REVIEW] Primal, freeform On IOS</h2>
<p>ในบทความนี้เป็นการรีวิวการใช้ client ต่าง ๆ บน IOS ของผมและเพื่อน ๆ ที่ช่วยกันลองเล่นและทดสอบเพื่อหา client บน IOS ที่จะใช้ป้ายยาคนใหม่ ๆ ให้เขาไม่รู้สึกว่าการเข้ามาในทุ่งม่วงแห่งนี้เป็นเรื่องยาก เมื่อได้ทดลองแล้วเลยอยากมาแชร์ผลที่ได้กับทุก ๆ คนบน Nostr เพื่อเป็นแนวทางหรือเป็นส่วนประกอบการตัดสินใจในการเข้าไปลองเล่น client ใหม่ ๆ </p>
<blockquote>
<p>ทั้งนี้ทั้งนั้น สิ่งที่ปรากฏภายในบทความนี้เป็นเพียงแค่ความคิดเห็นของผู้เขียนและแอพต่าง ๆ ที่ลอง<br>เป็นเวอร์ชันก่อนวันที่ 23/10/2023 หากโหลดมาใช้แล้วไม่เหมือนในรีวิวแปลว่าแอพมันอัปเดตไปแล้วนะครับ</p>
</blockquote>
<h2>Primal</h2>
<p>Primal เป็นอีก client นึงที่มีฟังก์ชันต่าง ๆ ค่อนข้างครบครันและมี UX/UI ที่ดี สวยงามและปรับแต่งได้ในระดับหนึ่ง นอกจากนี้ยังสามารถ zap ผ่าน โน้ตต่าง ๆ ได้โดยตรงและนอกจากนี้ยังรองรับ one-click zap ผ่าน NWC ได้อีกด้วย ส่วนการดาวน์โหลดในปัจจุบันสามารถทำได้ดังนี้</p>
<blockquote>
<p>Primal ในตอนนี้ทาง IOS สามารถดาวน์โหลดได้ผ่านทาง TestFlight เท่านั้น โดยสามารถเข้าไปรับ redeem code ได้ทางเว็บไซต์ของ primal จากนั้นให้ปุ่มดาวน์โหลดบน navbar แล้วเลือก IOS จากนั้นจะมีหน้าต่างโปรแกรม TestFlight ขึ้นมาให้เราดาวน์โหลด</p>
</blockquote>
<ul>
<li><ul>
<li>หากไม่เคยใช้ TestFlight มาก่อน แนะนำว่าให้ทำการดาวน์โหลดและ login ให้เสร็จเรียบร้อยก่อนไปรับ redeem code บน primal<br>ส่วนในแง่ของ ข้อดี – ข้อเสีย ที่ผมพอสรุปได้จากการใช้งานมีประมาณนี้ครับ</li>
</ul>
</li>
</ul>
<h3>ข้อดี</h3>
<ul>
<li>UI สวยงาม ตัวแอปลื่นไหลไม่สะดุดรองรับฟังก์ชันพื้นฐานครบ</li>
<li>Zap ผ่านโน้ตได้ และรับรอง NWC เพื่อทำ one-click-zap ได้</li>
<li>มีการแจ้งเตือนครบครันและปรับแต่งได้ตามความต้องการ</li>
<li>สร้างฟีดของ # ต่าง ๆ ได้ (ตรงนี้จะแตกต่างจากการติดตาม # ของ client อื่น ๆ )</li>
<li>UI ต่าง ๆ ของแอพเช่น ขนาดตัวอักษรสามารถปรับขนาดได้</li>
</ul>
<h2>ข้อเสีย</h2>
<ul>
<li>จัดการรีเลย์ผ่านทางแอพไม่ได้ อาจต้องมี client อื่น ๆ บนเครื่องเพื่อทำการจัดการต่าง ๆ เกี่ยวกับรีเลย์</li>
<li>การแจ้งเตือนในบางครั้งซ้ำซ้อนมีแจ้งเตือนเดิม ๆ เด้งหลาย ๆ ครั้ง</li>
<li>การแสดงจำนวนเช่น ผู้ติดตาม, คนตอบกลับ, คนกดไลค์มากกว่าความเป็นจริง</li>
<li>Upload วีดีโอตรง ๆ ไม่ได้ (เป็นลิ้งได้ครับ)</li>
<li>เวลาเราโพสต์และติด tag event ของโพสต์เราจะไม่มี tag ในช่อง tag ทำให้คนที่ติดตาม tag บน client อื่น ๆ ไม่เห็นโพสต์เรา (ร้ายแรง)</li>
</ul>
<h3>ข้อสรุป</h3>
<p>Primal เป็นอีก client นึงที่เหมาะสมกับการแนะนำมือใหม่มาก ๆ ครับเพราะถ้าสมัครบน primal มันจะเก็บ keys เข้าไปในมือถือเราไม่ต้องให้เรา copy ไปเก็บเองเหมือน client อื่น ๆ และนอกจากนี้ยังไม่ต้องให้มือใหม่ปวดหัวกับการจัดการรีเลย์ในช่วงแรกที่เล่นได้อีกด้วย </p>
<h2>Freefrom</h2>
<p>สำหรับทวิตเตอร์ เอ้ย freefrom เป็นอีก client ที่ใช้งานง่าย UI เป็นที่คุ้นเคย (ทวิต) แบบแทบจะเหมือนกันเลย และสามารถใช้งานฟังก์ชันต่าง ๆ ได้เหมือนกับโซเชี่ยลมีเดียโดยทั่วไปจุดเด่นที่ชอบในตัวของ freeform คือการที่เราสามารถส่ง contact card และ album ให้กันได้ด้วย ส่วนการดาวน์โหลดในปัจจุบันสามารถทำได้ดังนี้</p>
<blockquote>
<p>ตอนนี้สามารถดาวน์โหลดได้ผ่านทาง App Store ได้ตรง ๆ เลยครับ รูปนกตัวอ้วนกว่านกฟ้านิดนึง</p>
</blockquote>
<h3>ข้อดี</h3>
<ul>
<li>UI สวยงาม ใช้งานง่าย และหลาย ๆ คนน่าจะคุ้นเคยกับ UI รูปแบบนี้อยู่แล้ว</li>
<li>ระบบ DM สวยและมีลูกเล่นเยอะเช่น การส่ง contact card, album</li>
<li>Reaction ด้วย emoji ได้หลากหลายเป็นพื้นฐานอยู่แล้ว</li>
</ul>
<h3>ข้อเสีย</h3>
<ul>
<li>ไม่สามารถ zap หรือทำอะไรเกี่ยวกับ Lightning ได้เลยบนแอพ</li>
<li>ฟังก์ชันต่าง ๆ ที่เล่นได้ค่อนข้างน้อย  ไม่รองรับ Follow # หรือการ Upload video ไม่ได้ (ต้องเป็นลิ้ง)</li>
</ul>
<h3>ข้อสรุป</h3>
<p>Freeform เป็น client ที่เหมาะสมกับการแนะนำมือใหม่มาก ๆ โดยเฉพาะกลุ่มคนที่ไม่ได้เข้าใจ Bitcoin หรือ lightning เข้ามาเริ่มก่อนได้ง่าย ๆ และหากเขาสร้างคุณค่าในสังคมมากพอที่คนอยากจะ zap ให้เขา เดี๋ยวเขาก็หาทางใช้ client อื่น ๆ เพื่อมารับ zap เอง ;)</p>
<hr>
<p>ทั้งนี้ที่บทความนี้เกิดขึ้นมาได้ต้องขอขอบคุณ <a href="https://njump.me/npub1mqcwu7muxz3kfvfyfdme47a579t8x0lm3jrjx5yxuf4sknnpe43q7rnz85">Jakk Goodday</a> ที่แนะนำ primal มา และขอบคุณ <a href="https://njump.me/npub1r6g3v8nd0xmu3w080qaeevwz5pzk3qfh7vdx7ayd7dv0szr96ywsh7dlap">🐰🤍</a> ที่ยอมกระโดดมาเล่น Nostr และ ยอมช่วยทดลองและมารีวิวช่วยกันเทสจนสามารถได้บทความนี้ขึ้นมา</p>
]]></content:encoded>
      <itunes:author><![CDATA[123🦈ปลาฉลามขึ้นบก]]></itunes:author>
      <itunes:summary><![CDATA[<h2>[REVIEW] Primal, freeform On IOS</h2>
<p>ในบทความนี้เป็นการรีวิวการใช้ client ต่าง ๆ บน IOS ของผมและเพื่อน ๆ ที่ช่วยกันลองเล่นและทดสอบเพื่อหา client บน IOS ที่จะใช้ป้ายยาคนใหม่ ๆ ให้เขาไม่รู้สึกว่าการเข้ามาในทุ่งม่วงแห่งนี้เป็นเรื่องยาก เมื่อได้ทดลองแล้วเลยอยากมาแชร์ผลที่ได้กับทุก ๆ คนบน Nostr เพื่อเป็นแนวทางหรือเป็นส่วนประกอบการตัดสินใจในการเข้าไปลองเล่น client ใหม่ ๆ </p>
<blockquote>
<p>ทั้งนี้ทั้งนั้น สิ่งที่ปรากฏภายในบทความนี้เป็นเพียงแค่ความคิดเห็นของผู้เขียนและแอพต่าง ๆ ที่ลอง<br>เป็นเวอร์ชันก่อนวันที่ 23/10/2023 หากโหลดมาใช้แล้วไม่เหมือนในรีวิวแปลว่าแอพมันอัปเดตไปแล้วนะครับ</p>
</blockquote>
<h2>Primal</h2>
<p>Primal เป็นอีก client นึงที่มีฟังก์ชันต่าง ๆ ค่อนข้างครบครันและมี UX/UI ที่ดี สวยงามและปรับแต่งได้ในระดับหนึ่ง นอกจากนี้ยังสามารถ zap ผ่าน โน้ตต่าง ๆ ได้โดยตรงและนอกจากนี้ยังรองรับ one-click zap ผ่าน NWC ได้อีกด้วย ส่วนการดาวน์โหลดในปัจจุบันสามารถทำได้ดังนี้</p>
<blockquote>
<p>Primal ในตอนนี้ทาง IOS สามารถดาวน์โหลดได้ผ่านทาง TestFlight เท่านั้น โดยสามารถเข้าไปรับ redeem code ได้ทางเว็บไซต์ของ primal จากนั้นให้ปุ่มดาวน์โหลดบน navbar แล้วเลือก IOS จากนั้นจะมีหน้าต่างโปรแกรม TestFlight ขึ้นมาให้เราดาวน์โหลด</p>
</blockquote>
<ul>
<li><ul>
<li>หากไม่เคยใช้ TestFlight มาก่อน แนะนำว่าให้ทำการดาวน์โหลดและ login ให้เสร็จเรียบร้อยก่อนไปรับ redeem code บน primal<br>ส่วนในแง่ของ ข้อดี – ข้อเสีย ที่ผมพอสรุปได้จากการใช้งานมีประมาณนี้ครับ</li>
</ul>
</li>
</ul>
<h3>ข้อดี</h3>
<ul>
<li>UI สวยงาม ตัวแอปลื่นไหลไม่สะดุดรองรับฟังก์ชันพื้นฐานครบ</li>
<li>Zap ผ่านโน้ตได้ และรับรอง NWC เพื่อทำ one-click-zap ได้</li>
<li>มีการแจ้งเตือนครบครันและปรับแต่งได้ตามความต้องการ</li>
<li>สร้างฟีดของ # ต่าง ๆ ได้ (ตรงนี้จะแตกต่างจากการติดตาม # ของ client อื่น ๆ )</li>
<li>UI ต่าง ๆ ของแอพเช่น ขนาดตัวอักษรสามารถปรับขนาดได้</li>
</ul>
<h2>ข้อเสีย</h2>
<ul>
<li>จัดการรีเลย์ผ่านทางแอพไม่ได้ อาจต้องมี client อื่น ๆ บนเครื่องเพื่อทำการจัดการต่าง ๆ เกี่ยวกับรีเลย์</li>
<li>การแจ้งเตือนในบางครั้งซ้ำซ้อนมีแจ้งเตือนเดิม ๆ เด้งหลาย ๆ ครั้ง</li>
<li>การแสดงจำนวนเช่น ผู้ติดตาม, คนตอบกลับ, คนกดไลค์มากกว่าความเป็นจริง</li>
<li>Upload วีดีโอตรง ๆ ไม่ได้ (เป็นลิ้งได้ครับ)</li>
<li>เวลาเราโพสต์และติด tag event ของโพสต์เราจะไม่มี tag ในช่อง tag ทำให้คนที่ติดตาม tag บน client อื่น ๆ ไม่เห็นโพสต์เรา (ร้ายแรง)</li>
</ul>
<h3>ข้อสรุป</h3>
<p>Primal เป็นอีก client นึงที่เหมาะสมกับการแนะนำมือใหม่มาก ๆ ครับเพราะถ้าสมัครบน primal มันจะเก็บ keys เข้าไปในมือถือเราไม่ต้องให้เรา copy ไปเก็บเองเหมือน client อื่น ๆ และนอกจากนี้ยังไม่ต้องให้มือใหม่ปวดหัวกับการจัดการรีเลย์ในช่วงแรกที่เล่นได้อีกด้วย </p>
<h2>Freefrom</h2>
<p>สำหรับทวิตเตอร์ เอ้ย freefrom เป็นอีก client ที่ใช้งานง่าย UI เป็นที่คุ้นเคย (ทวิต) แบบแทบจะเหมือนกันเลย และสามารถใช้งานฟังก์ชันต่าง ๆ ได้เหมือนกับโซเชี่ยลมีเดียโดยทั่วไปจุดเด่นที่ชอบในตัวของ freeform คือการที่เราสามารถส่ง contact card และ album ให้กันได้ด้วย ส่วนการดาวน์โหลดในปัจจุบันสามารถทำได้ดังนี้</p>
<blockquote>
<p>ตอนนี้สามารถดาวน์โหลดได้ผ่านทาง App Store ได้ตรง ๆ เลยครับ รูปนกตัวอ้วนกว่านกฟ้านิดนึง</p>
</blockquote>
<h3>ข้อดี</h3>
<ul>
<li>UI สวยงาม ใช้งานง่าย และหลาย ๆ คนน่าจะคุ้นเคยกับ UI รูปแบบนี้อยู่แล้ว</li>
<li>ระบบ DM สวยและมีลูกเล่นเยอะเช่น การส่ง contact card, album</li>
<li>Reaction ด้วย emoji ได้หลากหลายเป็นพื้นฐานอยู่แล้ว</li>
</ul>
<h3>ข้อเสีย</h3>
<ul>
<li>ไม่สามารถ zap หรือทำอะไรเกี่ยวกับ Lightning ได้เลยบนแอพ</li>
<li>ฟังก์ชันต่าง ๆ ที่เล่นได้ค่อนข้างน้อย  ไม่รองรับ Follow # หรือการ Upload video ไม่ได้ (ต้องเป็นลิ้ง)</li>
</ul>
<h3>ข้อสรุป</h3>
<p>Freeform เป็น client ที่เหมาะสมกับการแนะนำมือใหม่มาก ๆ โดยเฉพาะกลุ่มคนที่ไม่ได้เข้าใจ Bitcoin หรือ lightning เข้ามาเริ่มก่อนได้ง่าย ๆ และหากเขาสร้างคุณค่าในสังคมมากพอที่คนอยากจะ zap ให้เขา เดี๋ยวเขาก็หาทางใช้ client อื่น ๆ เพื่อมารับ zap เอง ;)</p>
<hr>
<p>ทั้งนี้ที่บทความนี้เกิดขึ้นมาได้ต้องขอขอบคุณ <a href="https://njump.me/npub1mqcwu7muxz3kfvfyfdme47a579t8x0lm3jrjx5yxuf4sknnpe43q7rnz85">Jakk Goodday</a> ที่แนะนำ primal มา และขอบคุณ <a href="https://njump.me/npub1r6g3v8nd0xmu3w080qaeevwz5pzk3qfh7vdx7ayd7dv0szr96ywsh7dlap">🐰🤍</a> ที่ยอมกระโดดมาเล่น Nostr และ ยอมช่วยทดลองและมารีวิวช่วยกันเทสจนสามารถได้บทความนี้ขึ้นมา</p>
]]></itunes:summary>
      <itunes:image href="https://cdn.discordapp.com/attachments/1120171425433661440/1165969352005259365/a-purple-ostrich-in-a-meadow-full-of-fun-and-adven-upscaled.png?ex=6548c894&amp;is=65365394&amp;hm=674b41cb27807180a549500c7d5c29a4ab4d2c7e68ae3aee7b818aaa693159f5&amp;"/>
      </item>
      
      <item>
      <title><![CDATA[Ransomware มันจะเท่าไหร่กันเชียว]]></title>
      <description><![CDATA[ความทรงจำในแดนอาทิตย์อุทัยที่มีแต่งาน]]></description>
             <itunes:subtitle><![CDATA[ความทรงจำในแดนอาทิตย์อุทัยที่มีแต่งาน]]></itunes:subtitle>
      <pubDate>Sat, 30 Nov 56548 13:20:50 GMT</pubDate>
      <link>https://kritta.npub.pro/post/8eodlc9aest5yv9ssvgvo/</link>
      <comments>https://kritta.npub.pro/post/8eodlc9aest5yv9ssvgvo/</comments>
      <guid isPermaLink="false">naddr1qq2nset0v3xyxwtpv4e4gd2ewcu4xu6kvamy7q3qvm0kq43djwdd4psjgdjgn9z6fm836c35dv7eg7x74z3n3ueq83jqxpqqqp65wrsq5ye</guid>
      <category>หลามมันร้าย</category>
      
        <media:content url="https://image.nostr.build/8ccc372dad68a8f7a73365da8f1d181411919b41685cd8e1ff5b8072d062d565.jpg" medium="image"/>
        <enclosure 
          url="https://image.nostr.build/8ccc372dad68a8f7a73365da8f1d181411919b41685cd8e1ff5b8072d062d565.jpg" length="0" 
          type="image/jpeg" 
        />
      <noteId>naddr1qq2nset0v3xyxwtpv4e4gd2ewcu4xu6kvamy7q3qvm0kq43djwdd4psjgdjgn9z6fm836c35dv7eg7x74z3n3ueq83jqxpqqqp65wrsq5ye</noteId>
      <npub>npub1vm0kq43djwdd4psjgdjgn9z6fm836c35dv7eg7x74z3n3ueq83jqhkxp8e</npub>
      <dc:creator><![CDATA[123🦈ปลาฉลามขึ้นบก]]></dc:creator>
      <content:encoded><![CDATA[<p>แนวคิดอันนี้เกิดขึ้นหลังจากที่ผมได้รู้จัก LFS หรือ Log- structure File System มันทำให้ผมเกิดความคิดว่าในเมื่อมันสามารถที่จะเก็บ file และทุก log ของการเปลี่ยนแปลงในแต่ละไฟล์ งั้นแปลว่าในกรณีที่ file โดน encrypt มันก็น่าจะสามารถกู้คืนมาได้สิ เอ้ะแต่ถ้าเครื่องนั้นโดน ransomware ไปรอบนึงแล้วกู้คืนมาแล้วมันก็โดนอีกอยู่ดีมั้ย เลยเกิดไอเดียใหม่ว่าแล้วถ้าเราหา SSD อีกลูกมาเก็บ backup ถ้าโดน ransomware ก็ถอดไปเสียบเครื่องใหม่ก็จบ ตะตะแต่ว่า LFS เนี่ย ดั้นนนใช้ไม่ได้กับ windows หรือ Mac เอาละ ปัญหาใหม่มาอีกละ งั้นต้องทำไงล่ะ ? หึเก็บไว้บอกท้ายบทความละกัน มาทำความเข้าใจโปรเจคนี้กันทีละขั้นตอนกันดีกว่า</p>
<h2>Log-structure File System (LFS)</h2>
<p>เป็น File system รูปแบบหนึ่งที่จะทำการเก็บข้อมูลเป็น log และสามารถย้อนกลับได้ทุกการกระทำ นอกจากมีความถี่ในการสร้าง snapshot ที่สูงมาก จริง ๆ แล้วเหมาะกับงานประเภทที่ต้องการความต่อเนื่องหากดบัไปต้องกู้คืนได้มากที่สุดเท่าที่เป็นไปได้ แต่เพราะไอความสามารถตรงนั้นของมันนี่แหละ ที่ผมมองว่ามันน่าจะหยิบมาใช้กับการกู้คืน file หลังจากโดน ransomware ได้แน่ ๆ โดยในครั้งนี้ผมเลือกใช้เป็น NILFS2 เนื่องจากดูแล้วน่าจะเหลือตัวเดียวที่ยังพอมีการพัฒนาอยู่ 5555 รูปแบบการเก็บข้อมูลจะเหมือนกับภาพด้านล่างนี้ </p>
<p><img src="https://sarwiki.informatik.hu-berlin.de/images/a/a8/Lfs-layout-log.png" alt="image"></p>
<p>ตัวอย่าง:<br>เดิมที file A: "AAAAA" จากนั้นผมได้ทำการอัพเดตข้อมูลใน file A เป็น "BBBBB" ด้วยความสามารถของ LFS มันสามารถที่จะดึง file A เวอร์ชันที่เป็น "AAAAA" กลับมาได้</p>
<p>นี่เป็นปัจจัยสำคัญที่ทำให้ผมมองว่า LFS เหมาะสมที่จะมาเป็นระบบไฟล์ที่ใช้ในการสำรองข้อมูล แต่อยากที่ได้กล่าวไว้ข้างต้นว่าปัญหาของระบบไฟล์นี้คือมันใช้พื้นที่เก็บข้อมูลค่อนข้างสูง และมี write speed ที่ค่อนข้างช้า ถ้าเทียบกับระบบไฟล์อื่น ๆ ในกรณีที่ความเร็วของ SSD เท่ากัน เลยมองว่าถ้าจะใช้เป็นระบบหลักในการเก็บข้อมูลทุกอย่างคงจะไม่เหมาะ เลยนำไปสู่การทำระบบโดยใช้ระบบไฟล์สองแบบที่แตกต่างกัน</p>
<h2>System Design</h2>
<p>อย่างที่ได้พูดถึงข้อเสียของ NILFS2 ไปข้างต้น แปลว่าถ้าอยากจะให้คอมพิวเตอร์ทำงานได้อย่างมีประสิทธิภาพคงเดิมและสามารถป้องกัน ransomware ได้ด้วยก็จำเป็นต้องมีระบบไฟล์ที่ใช้กันทั่วไปอย่าง FAT32 หรือ ext4 และใช้ SSD อีกลูกหนึ่งที่ format เป็น NILFS2 และเขียนโปรแกรมให้คอยมอนิเตอร์ทุก ๆ กิจกรรมบน user space และทุกครั้งที่เกิดการเขียนไฟล์ขึ้นให้เขียนลงใน SSD ที่เป็น NILFS2 ด้วย ดังภาพที่แสดงด้านล่างนี้</p>
<p><img src="https://yakihonne.s3.ap-east-1.amazonaws.com/66df60562d939ada8612436489945a4ecf1d62346b3d9478dea8a338f3203c64/files/1722426766185-YAKIHONNES3.png" alt="image"></p>
<p>แล้วทำไมไฟล์ในลูกที่เก็บสำรองข้อมูลถึงไม่เท่ากับจำนวนไฟล์ในที่เก็บข้อมูลหลักล่ะ ? เนื่องด้วยความเร็วในการเขียนที่ค่อนข้างแตกต่างกันของทั้งสอง ผมเลยมองว่าถ้าเราเก็บในรูปแบบบล๊อกจะทำให้เก็บข้อมูลได้ไวกว่า อย่างตัวอย่างในรูป สมมุติว่าไฟล์ A มีขนาดประมาณ 3 บล๊อกใน SSD ที่เป็น nilfs2 ก็จะมี 3 ไฟล์และต้องใช้ทั้งสามไฟล์เพื่อที่จะคืนค่าไฟล์ A กลับมา ถ้าอ่านมาถึงตรงนี้อาจจะเกิดข้อสงสัยว่า แล้วจะทำให้มันยุ่งยากทำไม คัดลอกไฟล์มาใส่ก็จบแล้วมั้ย ? ก็ใช่แหละและจริง ๆ ผมคิดว่าทำแบบนั้นก็เพียงพอ แต่ว่าอย่างที่บอกไปข้างต้น ผมอยากได้ระบบที่ป้องกันได้ 100% ผมเลยเลือกที่จะมอนิเตอร์ kernel เอาไว้และเมื่อมีการเขียนไฟล์เกิดขึ้นผมต้องการดึงข้อมูลมาจากตรงนั้นเลย ไม่อยากรอถูกเขียนลงในพื้นที่เก็บข้อมูลหลักก่อนแล้วค่อยคัดลอกมา จริง ๆ ก็ด้วยเหตุผลด้านประสิทธิภาพด้วย </p>
<h2>แล้วจะมอนิเตอร์ kernel อย่างไร ??</h2>
<p>ในครั้งนี้ผมเลือกที่จะใช้เป็น eBPF (extended Berkeley Packet Filter) จริงแล้วเจ้า eBPF เนี่ยใช้งานได้หลากหลายเลยแหละ เรียกได้ว่าประโยชน์รอบด้านสุด ๆ ตามรูปที่แนบด้านล่างเลย</p>
<p><img src="https://ebpf.io/static/e293240ecccb9d506587571007c36739/ea28c/overview.webp" alt="image"></p>
<p>แต่ในครั้งนี้เราจะยุ่งกับมันเพียงแค่การใช้งานเพื่อสอดส่อง kernel เท่านั้น</p>
<p><img src="https://ebpf.io/static/99c69bbff092c35b9c83f00a80fed240/fb370/hook-overview.webp" alt="image"></p>
<p>ภาพด้านบนนี้แสดงให้เห็นจุดที่เราสามารถใช้ eBPF ในการดึงข้อมูลได้ นั่นเลยเป็นเหตุผลหลักที่ผมเลือกใช้ โดยวางการทำงานทั้งหมดของการมอนิเตอร์ไว้ดังนี้</p>
<ol>
<li>ใช้ eBPF เพื่อคอยเช็คว่ามีการเขียนเกิดขึ้นมั้ยโดยการอ่านค่า flag จาก NVMe ว่ามีค่าเป็นอะไรจากนั้นนำค่าที่ได้มา XOR ด้วย mask ถ้าค่าออกมาเป็น 1 แปลว่านั่นคือ write_operation (อาจแตกต่างกันแล้วแต่ version) </li>
<li>เมื่อเช็คค่าแล้วเป็น write_operation จากนั้นก็ให้ทำการอ่านเลข sector, และขนาดของข้อมูลที่ต้องการจะเขียน </li>
<li>จากนั้นเข้าไปอ่านต่อใน Bio ลงไปยัง Bio_vec และไป Bv_page ตามลำดับ เพื่ออ่านข้อมูลทั้งหมดที่มาจากคำสั่งในการเขียนครั้งนั้น ๆ<br> <img src="https://litux.nl/mirror/kerneldevelopment/0672327201/images/0672327201/graphics/13fig02.gif" alt="image"><br>*ภาพประกอบเพิ่มเติมเพื่อใครงงว่า Bio คืออะไรข้อมูลเวลาเราเขียนจะอยู่บน page</li>
<li>ส่งข้อมูลที่ได้ทั้งหมดออกมา</li>
</ol>
<p>เพียงเท่านี้เราก็ทำการดึงข้อมูลทุกครั้งที่เราเขียนได้แล้ว และจะนำมันเข้าสู่ขั้นตอนต่อไปในการเก็บข้อมูลลง SSD</p>
<h2>เก็บข้อมูลและส่งไป SSD ที่เป็น nilfs2</h2>
<p>เมื่อได้ข้อมูลมาจากการทำงานที่ได้กล่าวไว้ข้างบนแล้ว ก็จะได้ชุดข้อมูลมาชุดหนึ่ง ส่งออกมันมาจาก ebpf(C program) มาสู่ Python ต่อโดยมีขั้นตอนดังนี้</p>
<ol>
<li>นำเลข sector ที่ได้มาทำการคูณด้วยขนาดของ sector เพื่อให้เราได้ logical address มา จากนั้นให้นำ logical address หารด้วยขนาดของบล๊อกเพื่อให้ได้เลขบล๊อก</li>
<li>นำข้อมูลในแต่ละ sector มาเรียงลงในบล๊อกที่มันอยู่ละบันทึกลงไปใน SSD</li>
</ol>
<p>ตัวอย่างเพื่อให้เห็นภาพมากขึ้น<br>สมมุติว่าข้อมูลที่ส่งกลับมาขนาด 4097 B และเริ่มเขียนที่ sector 0<br>(กำหนดให้ 1 sector มีขนาด 512B และ 1 Block มี 8 sector)</p>
<p>4097/512 = 8.xx ก็แปลว่าข้อมูลนี้ต้องเก็บใน 9 sector คือ sector 0 ถึง 8<br>และ 8 sector รวมเป็น 1 block แปลว่าข้อมูลใน sector 0-7จะอยู่ใน block 0 และ sector 8 จะอยู่ใน block 1 แปลว่าโปรแกรมของผมจะสร้างมาสองไฟล์คือ 0,1 จากนั้นจะทำการเติม 0 ท้ายไฟล์ 1 จนมีขนาด 4096 B เท่ากับขนาดของ block</p>
<p>สำหรับคนที่งงว่า sector บล๊อกคืออะไร<br><img src="https://cstaleem.com/wp-content/uploads/2020/06/Hard-Disk-structure-in-OS.png" alt="image"></p>
<p>ให้ดูตรงรูปกลม ๆ ทางซ้ายมือจะเห็นได้ว่ามันเป็นหน่วยความจำในจานหมุน ซึ่ง sector คือหน่วยย่อยสุด และ sector รวม ๆ กันหลาย ๆ sector จะเป็น cluter บางที่ก็เรียก Tracks บางที่ก็ Block ซึ่งนี่แหละคือบล๊อกที่ผมหมายถึง</p>
<p>เพียงเท่านี้เราก็ได้ระบบที่สามารถป้องกัน ransomware ได้แล้ว เพราะเนื่องจากเมื่อเราโดน ransomware และไฟล์เราถูก encrypt เราก็เพียงใช้ความความสามารถของ SSD ลูกที่เป็น nilfs2 เพื่อนำไฟล์ดั้งเดิมก่อนจะถูก encrypt กับมาได้ดังรูปประกอบด้านล่างนี้</p>
<p><img src="https://yakihonne.s3.ap-east-1.amazonaws.com/66df60562d939ada8612436489945a4ecf1d62346b3d9478dea8a338f3203c64/files/1722431485024-YAKIHONNES3.png" alt="image"></p>
<h2>ส่งท้าย</h2>
<p>จริง ๆ แล้วที่ผมเก็บเป็นไฟล์ block เพราะผมอยากให้ backup file สามารถเข้ากันได้กับทุกระบบไฟล์เพื่อให้สามารถใช้งานได้กับทุกระบบ ถึงจะยังทำไม่ได้ก็เหอะ 55555 </p>
]]></content:encoded>
      <itunes:author><![CDATA[123🦈ปลาฉลามขึ้นบก]]></itunes:author>
      <itunes:summary><![CDATA[<p>แนวคิดอันนี้เกิดขึ้นหลังจากที่ผมได้รู้จัก LFS หรือ Log- structure File System มันทำให้ผมเกิดความคิดว่าในเมื่อมันสามารถที่จะเก็บ file และทุก log ของการเปลี่ยนแปลงในแต่ละไฟล์ งั้นแปลว่าในกรณีที่ file โดน encrypt มันก็น่าจะสามารถกู้คืนมาได้สิ เอ้ะแต่ถ้าเครื่องนั้นโดน ransomware ไปรอบนึงแล้วกู้คืนมาแล้วมันก็โดนอีกอยู่ดีมั้ย เลยเกิดไอเดียใหม่ว่าแล้วถ้าเราหา SSD อีกลูกมาเก็บ backup ถ้าโดน ransomware ก็ถอดไปเสียบเครื่องใหม่ก็จบ ตะตะแต่ว่า LFS เนี่ย ดั้นนนใช้ไม่ได้กับ windows หรือ Mac เอาละ ปัญหาใหม่มาอีกละ งั้นต้องทำไงล่ะ ? หึเก็บไว้บอกท้ายบทความละกัน มาทำความเข้าใจโปรเจคนี้กันทีละขั้นตอนกันดีกว่า</p>
<h2>Log-structure File System (LFS)</h2>
<p>เป็น File system รูปแบบหนึ่งที่จะทำการเก็บข้อมูลเป็น log และสามารถย้อนกลับได้ทุกการกระทำ นอกจากมีความถี่ในการสร้าง snapshot ที่สูงมาก จริง ๆ แล้วเหมาะกับงานประเภทที่ต้องการความต่อเนื่องหากดบัไปต้องกู้คืนได้มากที่สุดเท่าที่เป็นไปได้ แต่เพราะไอความสามารถตรงนั้นของมันนี่แหละ ที่ผมมองว่ามันน่าจะหยิบมาใช้กับการกู้คืน file หลังจากโดน ransomware ได้แน่ ๆ โดยในครั้งนี้ผมเลือกใช้เป็น NILFS2 เนื่องจากดูแล้วน่าจะเหลือตัวเดียวที่ยังพอมีการพัฒนาอยู่ 5555 รูปแบบการเก็บข้อมูลจะเหมือนกับภาพด้านล่างนี้ </p>
<p><img src="https://sarwiki.informatik.hu-berlin.de/images/a/a8/Lfs-layout-log.png" alt="image"></p>
<p>ตัวอย่าง:<br>เดิมที file A: "AAAAA" จากนั้นผมได้ทำการอัพเดตข้อมูลใน file A เป็น "BBBBB" ด้วยความสามารถของ LFS มันสามารถที่จะดึง file A เวอร์ชันที่เป็น "AAAAA" กลับมาได้</p>
<p>นี่เป็นปัจจัยสำคัญที่ทำให้ผมมองว่า LFS เหมาะสมที่จะมาเป็นระบบไฟล์ที่ใช้ในการสำรองข้อมูล แต่อยากที่ได้กล่าวไว้ข้างต้นว่าปัญหาของระบบไฟล์นี้คือมันใช้พื้นที่เก็บข้อมูลค่อนข้างสูง และมี write speed ที่ค่อนข้างช้า ถ้าเทียบกับระบบไฟล์อื่น ๆ ในกรณีที่ความเร็วของ SSD เท่ากัน เลยมองว่าถ้าจะใช้เป็นระบบหลักในการเก็บข้อมูลทุกอย่างคงจะไม่เหมาะ เลยนำไปสู่การทำระบบโดยใช้ระบบไฟล์สองแบบที่แตกต่างกัน</p>
<h2>System Design</h2>
<p>อย่างที่ได้พูดถึงข้อเสียของ NILFS2 ไปข้างต้น แปลว่าถ้าอยากจะให้คอมพิวเตอร์ทำงานได้อย่างมีประสิทธิภาพคงเดิมและสามารถป้องกัน ransomware ได้ด้วยก็จำเป็นต้องมีระบบไฟล์ที่ใช้กันทั่วไปอย่าง FAT32 หรือ ext4 และใช้ SSD อีกลูกหนึ่งที่ format เป็น NILFS2 และเขียนโปรแกรมให้คอยมอนิเตอร์ทุก ๆ กิจกรรมบน user space และทุกครั้งที่เกิดการเขียนไฟล์ขึ้นให้เขียนลงใน SSD ที่เป็น NILFS2 ด้วย ดังภาพที่แสดงด้านล่างนี้</p>
<p><img src="https://yakihonne.s3.ap-east-1.amazonaws.com/66df60562d939ada8612436489945a4ecf1d62346b3d9478dea8a338f3203c64/files/1722426766185-YAKIHONNES3.png" alt="image"></p>
<p>แล้วทำไมไฟล์ในลูกที่เก็บสำรองข้อมูลถึงไม่เท่ากับจำนวนไฟล์ในที่เก็บข้อมูลหลักล่ะ ? เนื่องด้วยความเร็วในการเขียนที่ค่อนข้างแตกต่างกันของทั้งสอง ผมเลยมองว่าถ้าเราเก็บในรูปแบบบล๊อกจะทำให้เก็บข้อมูลได้ไวกว่า อย่างตัวอย่างในรูป สมมุติว่าไฟล์ A มีขนาดประมาณ 3 บล๊อกใน SSD ที่เป็น nilfs2 ก็จะมี 3 ไฟล์และต้องใช้ทั้งสามไฟล์เพื่อที่จะคืนค่าไฟล์ A กลับมา ถ้าอ่านมาถึงตรงนี้อาจจะเกิดข้อสงสัยว่า แล้วจะทำให้มันยุ่งยากทำไม คัดลอกไฟล์มาใส่ก็จบแล้วมั้ย ? ก็ใช่แหละและจริง ๆ ผมคิดว่าทำแบบนั้นก็เพียงพอ แต่ว่าอย่างที่บอกไปข้างต้น ผมอยากได้ระบบที่ป้องกันได้ 100% ผมเลยเลือกที่จะมอนิเตอร์ kernel เอาไว้และเมื่อมีการเขียนไฟล์เกิดขึ้นผมต้องการดึงข้อมูลมาจากตรงนั้นเลย ไม่อยากรอถูกเขียนลงในพื้นที่เก็บข้อมูลหลักก่อนแล้วค่อยคัดลอกมา จริง ๆ ก็ด้วยเหตุผลด้านประสิทธิภาพด้วย </p>
<h2>แล้วจะมอนิเตอร์ kernel อย่างไร ??</h2>
<p>ในครั้งนี้ผมเลือกที่จะใช้เป็น eBPF (extended Berkeley Packet Filter) จริงแล้วเจ้า eBPF เนี่ยใช้งานได้หลากหลายเลยแหละ เรียกได้ว่าประโยชน์รอบด้านสุด ๆ ตามรูปที่แนบด้านล่างเลย</p>
<p><img src="https://ebpf.io/static/e293240ecccb9d506587571007c36739/ea28c/overview.webp" alt="image"></p>
<p>แต่ในครั้งนี้เราจะยุ่งกับมันเพียงแค่การใช้งานเพื่อสอดส่อง kernel เท่านั้น</p>
<p><img src="https://ebpf.io/static/99c69bbff092c35b9c83f00a80fed240/fb370/hook-overview.webp" alt="image"></p>
<p>ภาพด้านบนนี้แสดงให้เห็นจุดที่เราสามารถใช้ eBPF ในการดึงข้อมูลได้ นั่นเลยเป็นเหตุผลหลักที่ผมเลือกใช้ โดยวางการทำงานทั้งหมดของการมอนิเตอร์ไว้ดังนี้</p>
<ol>
<li>ใช้ eBPF เพื่อคอยเช็คว่ามีการเขียนเกิดขึ้นมั้ยโดยการอ่านค่า flag จาก NVMe ว่ามีค่าเป็นอะไรจากนั้นนำค่าที่ได้มา XOR ด้วย mask ถ้าค่าออกมาเป็น 1 แปลว่านั่นคือ write_operation (อาจแตกต่างกันแล้วแต่ version) </li>
<li>เมื่อเช็คค่าแล้วเป็น write_operation จากนั้นก็ให้ทำการอ่านเลข sector, และขนาดของข้อมูลที่ต้องการจะเขียน </li>
<li>จากนั้นเข้าไปอ่านต่อใน Bio ลงไปยัง Bio_vec และไป Bv_page ตามลำดับ เพื่ออ่านข้อมูลทั้งหมดที่มาจากคำสั่งในการเขียนครั้งนั้น ๆ<br> <img src="https://litux.nl/mirror/kerneldevelopment/0672327201/images/0672327201/graphics/13fig02.gif" alt="image"><br>*ภาพประกอบเพิ่มเติมเพื่อใครงงว่า Bio คืออะไรข้อมูลเวลาเราเขียนจะอยู่บน page</li>
<li>ส่งข้อมูลที่ได้ทั้งหมดออกมา</li>
</ol>
<p>เพียงเท่านี้เราก็ทำการดึงข้อมูลทุกครั้งที่เราเขียนได้แล้ว และจะนำมันเข้าสู่ขั้นตอนต่อไปในการเก็บข้อมูลลง SSD</p>
<h2>เก็บข้อมูลและส่งไป SSD ที่เป็น nilfs2</h2>
<p>เมื่อได้ข้อมูลมาจากการทำงานที่ได้กล่าวไว้ข้างบนแล้ว ก็จะได้ชุดข้อมูลมาชุดหนึ่ง ส่งออกมันมาจาก ebpf(C program) มาสู่ Python ต่อโดยมีขั้นตอนดังนี้</p>
<ol>
<li>นำเลข sector ที่ได้มาทำการคูณด้วยขนาดของ sector เพื่อให้เราได้ logical address มา จากนั้นให้นำ logical address หารด้วยขนาดของบล๊อกเพื่อให้ได้เลขบล๊อก</li>
<li>นำข้อมูลในแต่ละ sector มาเรียงลงในบล๊อกที่มันอยู่ละบันทึกลงไปใน SSD</li>
</ol>
<p>ตัวอย่างเพื่อให้เห็นภาพมากขึ้น<br>สมมุติว่าข้อมูลที่ส่งกลับมาขนาด 4097 B และเริ่มเขียนที่ sector 0<br>(กำหนดให้ 1 sector มีขนาด 512B และ 1 Block มี 8 sector)</p>
<p>4097/512 = 8.xx ก็แปลว่าข้อมูลนี้ต้องเก็บใน 9 sector คือ sector 0 ถึง 8<br>และ 8 sector รวมเป็น 1 block แปลว่าข้อมูลใน sector 0-7จะอยู่ใน block 0 และ sector 8 จะอยู่ใน block 1 แปลว่าโปรแกรมของผมจะสร้างมาสองไฟล์คือ 0,1 จากนั้นจะทำการเติม 0 ท้ายไฟล์ 1 จนมีขนาด 4096 B เท่ากับขนาดของ block</p>
<p>สำหรับคนที่งงว่า sector บล๊อกคืออะไร<br><img src="https://cstaleem.com/wp-content/uploads/2020/06/Hard-Disk-structure-in-OS.png" alt="image"></p>
<p>ให้ดูตรงรูปกลม ๆ ทางซ้ายมือจะเห็นได้ว่ามันเป็นหน่วยความจำในจานหมุน ซึ่ง sector คือหน่วยย่อยสุด และ sector รวม ๆ กันหลาย ๆ sector จะเป็น cluter บางที่ก็เรียก Tracks บางที่ก็ Block ซึ่งนี่แหละคือบล๊อกที่ผมหมายถึง</p>
<p>เพียงเท่านี้เราก็ได้ระบบที่สามารถป้องกัน ransomware ได้แล้ว เพราะเนื่องจากเมื่อเราโดน ransomware และไฟล์เราถูก encrypt เราก็เพียงใช้ความความสามารถของ SSD ลูกที่เป็น nilfs2 เพื่อนำไฟล์ดั้งเดิมก่อนจะถูก encrypt กับมาได้ดังรูปประกอบด้านล่างนี้</p>
<p><img src="https://yakihonne.s3.ap-east-1.amazonaws.com/66df60562d939ada8612436489945a4ecf1d62346b3d9478dea8a338f3203c64/files/1722431485024-YAKIHONNES3.png" alt="image"></p>
<h2>ส่งท้าย</h2>
<p>จริง ๆ แล้วที่ผมเก็บเป็นไฟล์ block เพราะผมอยากให้ backup file สามารถเข้ากันได้กับทุกระบบไฟล์เพื่อให้สามารถใช้งานได้กับทุกระบบ ถึงจะยังทำไม่ได้ก็เหอะ 55555 </p>
]]></itunes:summary>
      <itunes:image href="https://image.nostr.build/8ccc372dad68a8f7a73365da8f1d181411919b41685cd8e1ff5b8072d062d565.jpg"/>
      </item>
      
      </channel>
      </rss>
    