use std::str;
use bitcoin::script::{self};
use bitcoin::opcodes::all::{OP_PUSHBYTES_0, OP_IF, OP_ENDIF};
use bitcoin::ScriptBuf;
use bitcoin::Witness;
use serde::{Deserialize, Serialize};
use lazy_static::lazy_static;
use regex::bytes::Regex;
use crate::inscription_id::InscriptionId;
use crate::tag::Tag;
use crate::envelope::PROTOCOL_ID;
use crate::envelope::BODY_TAG;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Default)]
pub struct Inscription {
pub body: Option<Vec<u8>>,
pub content_encoding: Option<Vec<u8>>,
pub content_type: Option<Vec<u8>>,
pub delegate: Option<Vec<u8>>,
pub duplicate_field: bool,
pub incomplete_field: bool,
pub metadata: Option<Vec<u8>>,
pub metaprotocol: Option<Vec<u8>>,
pub parents: Vec<Vec<u8>>,
pub pointer: Option<Vec<u8>>,
pub properties: Option<Vec<u8>>,
pub rune: Option<Vec<u8>>,
pub unrecognized_even_field: bool,
}
impl Inscription {
pub fn new(content_type: Option<&str>, body: Option<&[u8]>) -> Self {
Inscription {
content_type: content_type.map(|ct| ct.as_bytes().to_vec()),
body: body.map(|b| b.to_vec()),
..Default::default()
}
}
pub fn body(&self) -> Option<&[u8]> {
Some(self.body.as_ref()?)
}
pub fn into_body(self) -> Option<Vec<u8>> {
self.body
}
pub fn content_length(&self) -> Option<usize> {
Some(self.body()?.len())
}
pub fn content_type(&self) -> Option<&str> {
str::from_utf8(self.content_type.as_ref()?).ok()
}
pub fn content_encoding(&self) -> Option<&str> {
str::from_utf8(self.content_encoding.as_ref()?).ok()
}
pub fn delegate(&self) -> Option<InscriptionId> {
InscriptionId::from_value(self.delegate.as_deref()?)
}
pub fn metaprotocol(&self) -> Option<&str> {
str::from_utf8(self.metaprotocol.as_ref()?).ok()
}
pub fn parents(&self) -> Vec<InscriptionId> {
self
.parents
.iter()
.filter_map(|parent| InscriptionId::from_value(parent))
.collect()
}
pub fn pointer(&self) -> Option<u64> {
let value = self.pointer.as_ref()?;
if value.iter().skip(8).copied().any(|byte| byte != 0) {
return None;
}
let pointer = [
value.first().copied().unwrap_or(0),
value.get(1).copied().unwrap_or(0),
value.get(2).copied().unwrap_or(0),
value.get(3).copied().unwrap_or(0),
value.get(4).copied().unwrap_or(0),
value.get(5).copied().unwrap_or(0),
value.get(6).copied().unwrap_or(0),
value.get(7).copied().unwrap_or(0),
];
Some(u64::from_le_bytes(pointer))
}
pub fn append_reveal_script_to_builder(&self, mut builder: script::Builder) -> script::Builder {
builder = builder
.push_opcode(OP_PUSHBYTES_0)
.push_opcode(OP_IF);
builder = builder.push_slice::<[u8; 7]>(PROTOCOL_ID);
Tag::ContentType.append(&mut builder, &self.content_type);
Tag::ContentEncoding.append(&mut builder, &self.content_encoding);
Tag::Metaprotocol.append(&mut builder, &self.metaprotocol);
Tag::Parent.append_array(&mut builder, &self.parents);
Tag::Delegate.append(&mut builder, &self.delegate);
Tag::Pointer.append(&mut builder, &self.pointer);
Tag::Metadata.append(&mut builder, &self.metadata);
Tag::Rune.append(&mut builder, &self.rune);
Tag::Properties.append(&mut builder, &self.properties);
if let Some(body) = &self.body {
builder = builder.push_slice::<[u8; 0]>(BODY_TAG);
for chunk in body.chunks(crate::tag::MAX_SCRIPT_ELEMENT_SIZE) {
builder = Tag::push_bytes_to_builder(builder, chunk);
}
}
builder.push_opcode(OP_ENDIF)
}
pub fn to_script(&self) -> ScriptBuf {
self.append_reveal_script_to_builder(script::Builder::new()).into_script()
}
pub fn to_witness(&self) -> Witness {
let script = self.to_script();
let mut witness = Witness::new();
witness.push(script.into_bytes());
witness.push([]);
witness
}
pub fn hidden(&self) -> bool {
const BVM_NETWORK: &[u8] = b"<body style=\"background:#F61;color:#fff;\">\
<h1 style=\"height:100%\">bvm.network</h1></body>";
lazy_static! {
static ref BRC_420: Regex = Regex::new(r"^\s*/content/[[:xdigit:]]{64}i\d+\s*$").unwrap();
}
self
.body()
.map(|body| BRC_420.is_match(body) || body.starts_with(BVM_NETWORK))
.unwrap_or_default()
|| self.metaprotocol.is_some()
}
}
pub struct InscriptionBuilder {
content_type: Option<String>,
body: Option<Vec<u8>>,
delegate: Option<InscriptionId>,
metadata: Option<Vec<u8>>,
metaprotocol: Option<String>,
parents: Vec<InscriptionId>,
pointer: Option<u64>,
rune: Option<Vec<u8>>,
}
impl InscriptionBuilder {
pub fn new() -> Self {
Self {
content_type: None,
body: None,
delegate: None,
metadata: None,
metaprotocol: None,
parents: Vec::new(),
pointer: None,
rune: None,
}
}
pub fn content_type(mut self, content_type: &str) -> Self {
self.content_type = Some(content_type.to_string());
self
}
pub fn body(mut self, body: &[u8]) -> Self {
self.body = Some(body.to_vec());
self
}
pub fn parent(mut self, parent: InscriptionId) -> Self {
self.parents.push(parent);
self
}
pub fn delegate(mut self, delegate: InscriptionId) -> Self {
self.delegate = Some(delegate);
self
}
pub fn metadata(mut self, metadata: &[u8]) -> Self {
self.metadata = Some(metadata.to_vec());
self
}
pub fn metaprotocol(mut self, metaprotocol: &str) -> Self {
self.metaprotocol = Some(metaprotocol.to_string());
self
}
pub fn pointer(mut self, pointer: u64) -> Self {
self.pointer = Some(pointer);
self
}
pub fn rune(mut self, rune: &[u8]) -> Self {
self.rune = Some(rune.to_vec());
self
}
pub fn build(self) -> Inscription {
Inscription {
content_type: self.content_type.map(|ct| ct.into_bytes()),
body: self.body,
delegate: self.delegate.map(|d| d.value()),
metadata: self.metadata,
metaprotocol: self.metaprotocol.map(|m| m.into_bytes()),
parents: self.parents.iter().map(|p| p.value()).collect(),
pointer: self.pointer.map(|p| {
let mut bytes = p.to_le_bytes().to_vec();
while bytes.last().copied() == Some(0) {
bytes.pop();
}
bytes
}),
rune: self.rune,
..Default::default()
}
}
}