Skip to content

Commit

Permalink
Support GIFs (#1013)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Dec 20, 2022
1 parent d47005f commit 365adb1
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 28 deletions.
2 changes: 1 addition & 1 deletion src/content.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[derive(Debug, PartialEq)]
pub(crate) enum Content<'a> {
Text(&'a str),
Png(&'a [u8]),
Image,
}
7 changes: 5 additions & 2 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,10 @@ impl Index {
self.client.get_block(&hash).into_option()
}

pub(crate) fn get_inscription_by_sat(&self, sat: Sat) -> Result<Option<Inscription>> {
pub(crate) fn get_inscription_by_sat(
&self,
sat: Sat,
) -> Result<Option<(InscriptionId, Inscription)>> {
let db = self.database.begin_read()?;
let table = db.open_table(SAT_TO_INSCRIPTION_ID)?;

Expand All @@ -439,7 +442,7 @@ impl Index {
Ok(
self
.get_inscription_by_inscription_id(Txid::from_inner(*txid))?
.map(|(inscription, _)| inscription),
.map(|(inscription, _)| (InscriptionId::from_inner(*txid), inscription)),
)
}

Expand Down
15 changes: 10 additions & 5 deletions src/inscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ impl Inscription {
{
"txt" => "text/plain;charset=utf-8",
"png" => "image/png",
"gif" => "image/gif",
other => {
return Err(anyhow!(
"unrecognized file extension `.{other}`, only .txt and .png accepted"
"unrecognized file extension `.{other}`, only .txt, .png and .gif accepted"
))
}
};
Expand Down Expand Up @@ -95,7 +96,7 @@ impl Inscription {

match self.content_type()? {
"text/plain;charset=utf-8" => Some(Content::Text(str::from_utf8(content).ok()?)),
"image/png" => Some(Content::Png(content)),
"image/png" | "image/gif" => Some(Content::Image),
_ => None,
}
}
Expand All @@ -104,8 +105,11 @@ impl Inscription {
Some(self.content.as_ref()?)
}

pub(crate) fn content_html(&self) -> Trusted<ContentHtml> {
Trusted(ContentHtml(self.content()))
pub(crate) fn content_html(&self, inscription_id: InscriptionId) -> Trusted<ContentHtml> {
Trusted(ContentHtml {
content: self.content(),
inscription_id,
})
}

pub(crate) fn content_size(&self) -> Option<usize> {
Expand All @@ -117,7 +121,7 @@ impl Inscription {
}

pub(crate) fn is_graphical(&self) -> bool {
matches!(self.content_type(), Some("image/png"))
matches!(self.content(), Some(Content::Image))
}
}

Expand Down Expand Up @@ -713,6 +717,7 @@ mod tests {
fn is_graphical() {
assert!(inscription("image/png", []).is_graphical());
assert!(!inscription("foo", []).is_graphical());
assert!(inscription("image/gif", []).is_graphical());
assert!(!Inscription::new(None, Some(Vec::new())).is_graphical());
}
}
13 changes: 6 additions & 7 deletions src/subcommand/server/templates/content.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
use super::*;

pub(crate) struct ContentHtml<'a>(pub(crate) Option<Content<'a>>);
pub(crate) struct ContentHtml<'a> {
pub(crate) content: Option<Content<'a>>,
pub(crate) inscription_id: InscriptionId,
}

impl<'a> Display for ContentHtml<'a> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self.0 {
match self.content {
Some(Content::Text(text)) => {
write!(f, "<p>")?;
text.escape(f, false)?;
write!(f, "</p>")
}
Some(Content::Png(png)) => write!(
f,
"<img src='data:image/png;base64,{}'>",
base64::encode(png)
),
Some(Content::Image) => write!(f, "<img src=/content/{}>", self.inscription_id),
None => write!(f, "<p>UNKNOWN</p>"),
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/subcommand/server/templates/inscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ mod tests {
fn png_inscription() {
pretty_assert_eq!(
InscriptionHtml {
inscription_id: InscriptionId::from_str("ec90757eb3b164aa43fc548faa2fa0c52025494f2c15d5ddf11260b4034ac6dc").unwrap(),
inscription_id: InscriptionId::from_str(
"ec90757eb3b164aa43fc548faa2fa0c52025494f2c15d5ddf11260b4034ac6dc"
)
.unwrap(),
inscription: inscription("image/png", [1; 100]),
satpoint: satpoint(1, 0),
}
Expand All @@ -68,7 +71,7 @@ mod tests {
<dt>location</dt>
<dd>1111111111111111111111111111111111111111111111111111111111111111:1:0</dd>
</dl>
<img src='data:image/png;base64,AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ=='>
<img src=/content/ec90757eb3b164aa43fc548faa2fa0c52025494f2c15d5ddf11260b4034ac6dc>
"
.unindent()
);
Expand Down
22 changes: 17 additions & 5 deletions src/subcommand/server/templates/sat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::*;
pub(crate) struct SatHtml {
pub(crate) sat: Sat,
pub(crate) blocktime: Blocktime,
pub(crate) inscription: Option<Inscription>,
pub(crate) inscription: Option<(InscriptionId, Inscription)>,
}

impl PageContent for SatHtml {
Expand Down Expand Up @@ -85,7 +85,13 @@ mod tests {
SatHtml {
sat: Sat(0),
blocktime: Blocktime::Confirmed(0),
inscription: Some(inscription("text/plain;charset=utf-8", "HELLOWORLD")),
inscription: Some((
InscriptionId::from_str(
"ec90757eb3b164aa43fc548faa2fa0c52025494f2c15d5ddf11260b4034ac6dc"
)
.unwrap(),
inscription("text/plain;charset=utf-8", "HELLOWORLD")
)),
}
.to_string(),
"
Expand Down Expand Up @@ -118,9 +124,15 @@ mod tests {
SatHtml {
sat: Sat(0),
blocktime: Blocktime::Confirmed(0),
inscription: Some(inscription(
"text/plain;charset=utf-8",
"<script>alert('HELLOWORLD');</script>",
inscription: Some((
InscriptionId::from_str(
"ec90757eb3b164aa43fc548faa2fa0c52025494f2c15d5ddf11260b4034ac6dc"
)
.unwrap(),
inscription(
"text/plain;charset=utf-8",
"<script>alert('HELLOWORLD');</script>",
)
)),
}
.to_string(),
Expand Down
2 changes: 1 addition & 1 deletion templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ <h1>Bitcoin-native NFTs</h1>
<h2>Latest Inscriptions</h2>
<div class=inscriptions>
%% for (inscription, id) in &self.inscriptions {
<a href=/inscription/{{id}}>{{inscription.content_html()}}</a>
<a href=/inscription/{{id}}>{{inscription.content_html(*id)}}</a>
%% }
</div>
%% }
Expand Down
2 changes: 1 addition & 1 deletion templates/inscription.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ <h1>Inscription {{ self.inscription_id }}</h1>
<dt>location</dt>
<dd>{{ self.satpoint }}</dd>
</dl>
{{ self.inscription.content_html() }}
{{ self.inscription.content_html(self.inscription_id) }}
4 changes: 2 additions & 2 deletions templates/sat.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ <h1>Sat {{ self.sat.n() }}</h1>
<dt>offset</dt><dd>{{ self.sat.third() }}</dd>
<dt>rarity</dt><dd><span class={{self.sat.rarity()}}>{{ self.sat.rarity() }}</span></dd>
<dt>time</dt><dd>{{ self.blocktime }}</dd>
%% if let Some(inscription) = &self.inscription {
%% if let Some((inscription_id, inscription)) = &self.inscription {
<dt>inscription</dt>
<dd>{{ inscription.content_html() }}</dd>
<dd>{{ inscription.content_html(*inscription_id) }}</dd>
%% }
</dl>
%% if self.sat.n() > 0 {
Expand Down
2 changes: 1 addition & 1 deletion templates/transaction.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ <h1>Transaction <span class=monospace>{{self.txid}}</span></h1>
%% if let Some(inscription) = &self.inscription {
<h2>Inscription</h2>
<a href=/inscription/{{self.txid}}>
{{ inscription.content_html() }}
{{ inscription.content_html(self.txid) }}
</a>
%% }
<h2>{{"Output".tally(self.transaction.output.len())}}</h2>
Expand Down
25 changes: 24 additions & 1 deletion tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ fn inscribe_unknown_file_extension() {
.write("pepe.jpg", [1; 520])
.rpc_server(&rpc_server)
.expected_exit_code(1)
.expected_stderr("error: unrecognized file extension `.jpg`, only .txt and .png accepted\n")
.expected_stderr("error: unrecognized file extension `.jpg`, only .txt, .png and .gif accepted\n")
.run();
}

Expand Down Expand Up @@ -669,3 +669,26 @@ fn transactions() {
.stdout_regex(format!(".*{}\t0\n.*", txid.trim()))
.run();
}

#[test]
fn inscribe_gif() {
let rpc_server = test_bitcoincore_rpc::spawn_with(Network::Regtest, "ord");
rpc_server.mine_blocks(1)[0].txdata[0].txid();

let stdout =
CommandBuilder::new("--chain regtest --index-sats wallet inscribe --file dolphin.gif")
.write("dolphin.gif", [1; 520])
.rpc_server(&rpc_server)
.stdout_regex("commit\t[[:xdigit:]]{64}\nreveal\t[[:xdigit:]]{64}\n")
.run();
let inscription_id = reveal_txid_from_inscribe_stdout(&stdout);

rpc_server.mine_blocks(1);

let ord_server = TestServer::spawn_with_args(&rpc_server, &["--index-sats"]);

ord_server.assert_response_regex(
"/sat/5000000000",
&format!(".*<dt>inscription</dt>\n <dd><img src=/content/{inscription_id}.*"),
)
}

0 comments on commit 365adb1

Please sign in to comment.