fix: use UTF-16 offsets for description commands

Correct link boundaries when astral Unicode characters, such as emoji, precede or occur inside command runs.
This commit is contained in:
Barodius1 2026-08-02 00:34:55 -07:00 committed by GitHub
parent 1a0fd8287d
commit 93555d6862
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,7 +1,16 @@
require "json" require "json"
require "uri" require "uri"
private def copy_string(str : String::Builder, iter : Iterator, count : Int) : Int private def utf16_length(str : String) : Int32
length = 0
str.each_codepoint do |cp|
length += cp > 0xFFFF ? 2 : 1
end
return length
end
private def copy_string(str : String::Builder, iter : Iterator, count : Int, *, utf16 : Bool = false) : Int
copied = 0 copied = 0
while copied < count while copied < count
cp = iter.next cp = iter.next
@ -21,7 +30,9 @@ private def copy_string(str : String::Builder, iter : Iterator, count : Int) : I
str << cp.chr str << cp.chr
end end
copied += 1 # YouTube's command offsets use UTF-16 code units. Characters outside the
# basic multilingual plane (such as most emoji) occupy two such units.
copied += (utf16 && cp > 0xFFFF) ? 2 : 1
end end
return copied return copied
@ -53,12 +64,12 @@ def parse_description(desc, video_id : String) : String?
# Copy the text chunk between this command and the previous if needed. # Copy the text chunk between this command and the previous if needed.
length = cmd_start - index length = cmd_start - index
index += copy_string(str, iter, length) index += copy_string(str, iter, length, utf16: true)
# We need to copy the command's text using the iterator # We need to copy the command's text using the iterator
# and the special function defined above. # and the special function defined above.
cmd_content = String.build(cmd_length) do |str2| cmd_content = String.build(cmd_length) do |str2|
copy_string(str2, iter, cmd_length) copy_string(str2, iter, cmd_length, utf16: true)
end end
link = cmd_content link = cmd_content
@ -70,7 +81,7 @@ def parse_description(desc, video_id : String) : String?
end end
# Copy the end of the string (past the last command). # Copy the end of the string (past the last command).
remaining_length = content.size - index remaining_length = utf16_length(content) - index
copy_string(str, iter, remaining_length) if remaining_length > 0 copy_string(str, iter, remaining_length, utf16: true) if remaining_length > 0
end end
end end