This commit is contained in:
stryan 2022-02-04 18:48:09 -05:00
commit afbb41ec43
1481 changed files with 543321 additions and 0 deletions

5
Gemfile Normal file
View File

@ -0,0 +1,5 @@
source 'https://rubygems.org'
gem 'discordrb'
gem 'mimemagic'
gem 'down'
gem 'astro_moon'

61
Gemfile.lock Normal file
View File

@ -0,0 +1,61 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
astro_moon (0.2)
discordrb (3.4.0)
discordrb-webhooks (~> 3.3.0)
ffi (>= 1.9.24)
opus-ruby
rest-client (>= 2.0.0)
websocket-client-simple (>= 0.3.0)
discordrb-webhooks (3.3.0)
rest-client (>= 2.1.0.rc1)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
down (5.2.4)
addressable (~> 2.8)
event_emitter (0.2.6)
ffi (1.15.5)
http-accept (1.7.0)
http-cookie (1.0.4)
domain_name (~> 0.5)
mime-types (3.4.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2022.0105)
mimemagic (0.4.3)
nokogiri (~> 1)
rake
netrc (0.11.0)
nokogiri (1.13.1-x86_64-linux)
racc (~> 1.4)
opus-ruby (1.0.1)
ffi
public_suffix (4.0.6)
racc (1.6.0)
rake (13.0.6)
rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
unf (0.1.4)
unf_ext
unf_ext (0.0.8)
websocket (1.2.9)
websocket-client-simple (0.5.1)
event_emitter
websocket
PLATFORMS
x86_64-linux
DEPENDENCIES
astro_moon
discordrb
down
mimemagic
BUNDLED WITH
2.3.3

69
folder.rb Normal file
View File

@ -0,0 +1,69 @@
class Folder
def initialize()
@steves = []
@fallen = []
@submissions = []
@idgen = 1
end
def add_steve(uid)
@steves.append(uid)
end
def curse(user)
@steves.delete(user.id)
@fallen.append(user.id)
end
def steve?(user)
return (@steves.include?(user) or @fallen.include?(user))
end
def submit(sub)
sub.set_id(@idgen)
@submissions.append(sub)
@idgen = @idgen + 1
end
def harass_steve()
if @steves.length <= 0
return nil
end
target = @steves.sample
if @submissions.length <= 0
return nil
end
message = @submissions.sample
i = 0
#until message.sender != target or i == 5 do
# message = @submissions.sample
# i = i + 1
#end
if i == 5
return nil
end
@submissions.delete(message)
if File.exist?(message.content)
return nil
end
if message.local
File.delete(message.content) if File.exist?(message.content)
end
return target,message
end
end
class Submission
def initialize(content,local,sender)
@local = local
@content = content
@sender = sender
end
def set_id(i)
@id = i
end
attr_reader :local,:content,:sender,:id
end

117
main.rb Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/ruby
require 'bundler/setup'
require 'discordrb'
require 'yaml'
require 'mimemagic'
require 'down'
require 'fileutils'
require 'time'
require 'logger'
require 'astro/moon'
require './folder.rb'
logger = Logger.new(STDOUT)
logger.level = Logger::INFO
config_obj = YAML::load_file( './config.yaml' )
logger.info("loaded config")
bot = Discordrb::Commands::CommandBot.new token: config_obj["bot_token"], prefix: "!folder"
folder = Folder.new
config_obj['steves'].each do |steve|
folder.add_steve(steve)
end
bot.message(private: true,contains: "!folder fall") do |event|
if folder.steve?(event.user)
folder.curse(event.user)
mem = bot.member(config_obj['server_id'],event.user.id)
mem.set_nick("Geoff","This Steve has seperated himself from the Ideal Steve")
event.respond("May Snen have Mercy on your Soul")
else
event.respond("Oh that you could be so blessed as to have that option")
end
end
bot.message(private: true,contains: "!folder rise") do |event|
if folder.steve?(event.user)
folder.bless(event.user)
mem = bot.member(config_obj['server_id'],event.user.id)
mem.set_nick("","Steve-ness restored")
event.respond("Welcome back to the winning team")
else
event.respond("Fat chance, non-Steve")
end
end
bot.message(private: true, contains: "!folder help") do |event|
event.respond("Commands: rise,fall,help")
end
bot.message(private: true) do |event|
(mem,au) = event.author
i = 1
if mem == nil
sender = au.username
else
sender = mem.username
end
event.message.attachments.each do |file|
res = MimeMagic.by_path(file.filename)
if res != nil
if res.image? or res.video?
download = Down.download(file.url)
FileUtils.mv(download.path, "./spool/#{download.original_filename}")
folder.submit(Submission.new("./spool/#{download.original_filename}",true,sender))
event.respond("Submission #{i} accepted")
i = i + 1
else
event.respond("File type #{res} not supported yet")
end
else
event.respond("Submission denied; attached file but I can't figure out the file type")
end
end
urls = URI.extract(event.content)
urls.each do |url|
mime_guess = MimeMagic.by_extension(url)
if mime_guess != nil
if mime_guess.image? or mime_guess.video?
download = Down.download(file.url)
FileUtils.mv(download.path, "./spool/#{download.original_filename}")
folder.submit(Submission.new("./spool/#{download.original_filename}",true,sender))
end
else
folder.submit(Submission.new(url,false,sender))
end
event.respond("Submission #{i} accepted")
i= i+1
end
end
logger.info("Starting bot")
bot.run(true)
loop do
timer = Astro::Moon.phase.phase * (3600 * (1 + rand(3)))
logger.info("sleeping for #{timer} seconds")
sleep timer
t = Time.new
if t.hour < 9 or t.hour > 2
next
end
sid,msg = folder.harass_steve()
if msg == nil
logger.info("no submissions ready")
next
end
steve = bot.users[sid]
logger.info("sending submission #{msg.id}")
if msg.local
steve.send_file(File.open(msg.content,'r'))
else
steve.pm(msg.content)
end
end

29
vendor/bundle/ruby/3.1.0/bin/nokogiri vendored Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env ruby.ruby3.1
#
# This file was generated by RubyGems.
#
# The application 'nokogiri' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
Gem.use_gemdeps
version = ">= 0.a"
str = ARGV.first
if str
str = str.b[/\A_(.*)_\z/, 1]
if str and Gem::Version.correct?(str)
version = str
ARGV.shift
end
end
if Gem.respond_to?(:activate_bin_path)
load Gem.activate_bin_path('nokogiri', 'nokogiri', version)
else
gem "nokogiri", version
load Gem.bin_path("nokogiri", "nokogiri", version)
end

29
vendor/bundle/ruby/3.1.0/bin/rake vendored Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env ruby.ruby3.1
#
# This file was generated by RubyGems.
#
# The application 'rake' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
Gem.use_gemdeps
version = ">= 0.a"
str = ARGV.first
if str
str = str.b[/\A_(.*)_\z/, 1]
if str and Gem::Version.correct?(str)
version = str
ARGV.shift
end
end
if Gem.respond_to?(:activate_bin_path)
load Gem.activate_bin_path('rake', 'rake', version)
else
gem "rake", version
load Gem.bin_path("rake", "rake", version)
end

29
vendor/bundle/ruby/3.1.0/bin/restclient vendored Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env ruby.ruby3.1
#
# This file was generated by RubyGems.
#
# The application 'rest-client' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
Gem.use_gemdeps
version = ">= 0.a"
str = ARGV.first
if str
str = str.b[/\A_(.*)_\z/, 1]
if str and Gem::Version.correct?(str)
version = str
ARGV.shift
end
end
if Gem.respond_to?(:activate_bin_path)
load Gem.activate_bin_path('rest-client', 'restclient', version)
else
gem "rest-client", version
load Gem.bin_path("rest-client", "restclient", version)
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,134 @@
current directory: /home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/gems/ffi-1.15.5/ext/ffi_c
/usr/bin/ruby.ruby3.1 -I /usr/lib64/ruby/3.1.0 -r ./siteconf20220204-28723-jqj0he.rb extconf.rb
checking for ffi_prep_closure_loc() in -lffi... yes
checking for ffi_prep_cif_var()... yes
checking for ffi_raw_call()... yes
checking for ffi_prep_raw_closure()... yes
checking for whether -pthread is accepted as LDFLAGS... yes
creating extconf.h
creating Makefile
current directory: /home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/gems/ffi-1.15.5/ext/ffi_c
make DESTDIR\= clean
current directory: /home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/gems/ffi-1.15.5/ext/ffi_c
make DESTDIR\=
compiling AbstractMemory.c
AbstractMemory.c:480:1: warning: memory_read_array_of_string defined but not used [-Wunused-function]
480 | memory_read_array_of_string(int argc, VALUE* argv, VALUE self)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
AbstractMemory.c:175:1: warning: memory_read_array_of_bool defined but not used [-Wunused-function]
175 | memory_read_array_of_##name(VALUE self, VALUE length) \
| ^~~~~~~~~~~~~~~~~~~~~
AbstractMemory.c:298:1: note: in expansion of macro NUM_OP
298 | NUM_OP(bool, unsigned char, rbffi_bool_value, rbffi_bool_new, NOSWAP);
| ^~~~~~
AbstractMemory.c:151:1: warning: memory_write_array_of_bool defined but not used [-Wunused-function]
151 | memory_write_array_of_##name(VALUE self, VALUE ary) \
| ^~~~~~~~~~~~~~~~~~~~~~
AbstractMemory.c:298:1: note: in expansion of macro NUM_OP
298 | NUM_OP(bool, unsigned char, rbffi_bool_value, rbffi_bool_new, NOSWAP);
| ^~~~~~
AbstractMemory.c:123:1: warning: memory_read_bool defined but not used [-Wunused-function]
123 | memory_read_##name(VALUE self) \
| ^~~~~~~~~~~~
AbstractMemory.c:298:1: note: in expansion of macro NUM_OP
298 | NUM_OP(bool, unsigned char, rbffi_bool_value, rbffi_bool_new, NOSWAP);
| ^~~~~~
AbstractMemory.c:115:1: warning: memory_get_bool defined but not used [-Wunused-function]
115 | memory_get_##name(VALUE self, VALUE offset) \
| ^~~~~~~~~~~
AbstractMemory.c:298:1: note: in expansion of macro NUM_OP
298 | NUM_OP(bool, unsigned char, rbffi_bool_value, rbffi_bool_new, NOSWAP);
| ^~~~~~
AbstractMemory.c:96:1: warning: memory_write_bool defined but not used [-Wunused-function]
96 | memory_write_##name(VALUE self, VALUE value) \
| ^~~~~~~~~~~~~
AbstractMemory.c:298:1: note: in expansion of macro NUM_OP
298 | NUM_OP(bool, unsigned char, rbffi_bool_value, rbffi_bool_new, NOSWAP);
| ^~~~~~
AbstractMemory.c:87:1: warning: memory_put_bool defined but not used [-Wunused-function]
87 | memory_put_##name(VALUE self, VALUE offset, VALUE value) \
| ^~~~~~~~~~~
AbstractMemory.c:298:1: note: in expansion of macro NUM_OP
298 | NUM_OP(bool, unsigned char, rbffi_bool_value, rbffi_bool_new, NOSWAP);
| ^~~~~~
AbstractMemory.c:175:1: warning: memory_read_array_of_longdouble defined but not used [-Wunused-function]
175 | memory_read_array_of_##name(VALUE self, VALUE length) \
| ^~~~~~~~~~~~~~~~~~~~~
AbstractMemory.c:262:1: note: in expansion of macro NUM_OP
262 | NUM_OP(longdouble, long double, rbffi_num2longdouble, rbffi_longdouble_new, NOSWAP);
| ^~~~~~
AbstractMemory.c:151:1: warning: memory_write_array_of_longdouble defined but not used [-Wunused-function]
151 | memory_write_array_of_##name(VALUE self, VALUE ary) \
| ^~~~~~~~~~~~~~~~~~~~~~
AbstractMemory.c:262:1: note: in expansion of macro NUM_OP
262 | NUM_OP(longdouble, long double, rbffi_num2longdouble, rbffi_longdouble_new, NOSWAP);
| ^~~~~~
AbstractMemory.c:123:1: warning: memory_read_longdouble defined but not used [-Wunused-function]
123 | memory_read_##name(VALUE self) \
| ^~~~~~~~~~~~
AbstractMemory.c:262:1: note: in expansion of macro NUM_OP
262 | NUM_OP(longdouble, long double, rbffi_num2longdouble, rbffi_longdouble_new, NOSWAP);
| ^~~~~~
AbstractMemory.c:115:1: warning: memory_get_longdouble defined but not used [-Wunused-function]
115 | memory_get_##name(VALUE self, VALUE offset) \
| ^~~~~~~~~~~
AbstractMemory.c:262:1: note: in expansion of macro NUM_OP
262 | NUM_OP(longdouble, long double, rbffi_num2longdouble, rbffi_longdouble_new, NOSWAP);
| ^~~~~~
AbstractMemory.c:96:1: warning: memory_write_longdouble defined but not used [-Wunused-function]
96 | memory_write_##name(VALUE self, VALUE value) \
| ^~~~~~~~~~~~~
AbstractMemory.c:262:1: note: in expansion of macro NUM_OP
262 | NUM_OP(longdouble, long double, rbffi_num2longdouble, rbffi_longdouble_new, NOSWAP);
| ^~~~~~
AbstractMemory.c:87:1: warning: memory_put_longdouble defined but not used [-Wunused-function]
87 | memory_put_##name(VALUE self, VALUE offset, VALUE value) \
| ^~~~~~~~~~~
AbstractMemory.c:262:1: note: in expansion of macro NUM_OP
262 | NUM_OP(longdouble, long double, rbffi_num2longdouble, rbffi_longdouble_new, NOSWAP);
| ^~~~~~
compiling ArrayType.c
compiling Buffer.c
compiling Call.c
compiling ClosurePool.c
compiling DynamicLibrary.c
compiling Function.c
Function.c: In function callback_invoke:
Function.c:484:14: warning: variable empty set but not used [-Wunused-but-set-variable]
484 | bool empty = false;
| ^~~~~
compiling FunctionInfo.c
FunctionInfo.c: In function fntype_initialize:
FunctionInfo.c:117:27: warning: variable rbConvention set but not used [-Wunused-but-set-variable]
117 | VALUE rbEnums = Qnil, rbConvention = Qnil, rbBlocking = Qnil;
| ^~~~~~~~~~~~
compiling LastError.c
compiling LongDouble.c
compiling MappedType.c
compiling MemoryPointer.c
compiling MethodHandle.c
compiling Platform.c
compiling Pointer.c
compiling Struct.c
compiling StructByValue.c
compiling StructLayout.c
StructLayout.c: In function struct_field_initialize:
StructLayout.c:99:9: warning: variable nargs set but not used [-Wunused-but-set-variable]
99 | int nargs;
| ^~~~~
compiling Thread.c
compiling Type.c
compiling Types.c
compiling Variadic.c
Variadic.c: In function variadic_initialize:
Variadic.c:101:11: warning: variable convention set but not used [-Wunused-but-set-variable]
101 | VALUE convention = Qnil;
| ^~~~~~~~~~
compiling ffi.c
linking shared-object ffi_c.so
current directory: /home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/gems/ffi-1.15.5/ext/ffi_c
make DESTDIR\= install
/usr/bin/install -c -m 0755 ffi_c.so ./.gem.20220204-28723-jrafwf

View File

@ -0,0 +1,232 @@
LD_LIBRARY_PATH=.:/usr/lib64 pkg-config --exists libffi
LD_LIBRARY_PATH=.:/usr/lib64 pkg-config --libs libffi |
=> "-L/usr/lib64/../lib64 -lffi \n"
LD_LIBRARY_PATH=.:/usr/lib64 "gcc -o conftest -I/usr/include/ruby-3.1.0/x86_64-linux-gnu -I/usr/include/ruby-3.1.0/ruby/backward -I/usr/include/ruby-3.1.0 -I. -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -g -fno-strict-aliasing -fPIC conftest.c -L. -L/usr/lib64 -L. -flto=auto -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby3.1 -lm -lc"
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: int main(int argc, char **argv)
4: {
5: return !!argv[argc];
6: }
/* end */
LD_LIBRARY_PATH=.:/usr/lib64 "gcc -o conftest -I/usr/include/ruby-3.1.0/x86_64-linux-gnu -I/usr/include/ruby-3.1.0/ruby/backward -I/usr/include/ruby-3.1.0 -I. -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -g -fno-strict-aliasing -fPIC conftest.c -L. -L/usr/lib64 -L. -flto=auto -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby3.1 -L/usr/lib64/../lib64 -lffi -lm -lc"
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: int main(int argc, char **argv)
4: {
5: return !!argv[argc];
6: }
/* end */
LD_LIBRARY_PATH=.:/usr/lib64 pkg-config --cflags-only-I libffi |
=> "\n"
LD_LIBRARY_PATH=.:/usr/lib64 pkg-config --cflags-only-other libffi |
=> "\n"
LD_LIBRARY_PATH=.:/usr/lib64 pkg-config --libs-only-l libffi |
=> "-lffi \n"
package configuration for libffi
incflags:
cflags:
ldflags: -L/usr/lib64/../lib64
libs: -lffi
have_library: checking for ffi_prep_closure_loc() in -lffi... -------------------- yes
LD_LIBRARY_PATH=.:/usr/lib64 "gcc -o conftest -I/usr/include/ruby-3.1.0/x86_64-linux-gnu -I/usr/include/ruby-3.1.0/ruby/backward -I/usr/include/ruby-3.1.0 -I. -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -g -fno-strict-aliasing -fPIC conftest.c -L. -L/usr/lib64 -L. -flto=auto -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -L/usr/lib64/../lib64 -lffi -lruby3.1 -lffi -lffi -lm -lc"
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: #include <ffi.h>
4:
5: /*top*/
6: extern int t(void);
7: int main(int argc, char **argv)
8: {
9: if (argc > 1000000) {
10: int (* volatile tp)(void)=(int (*)(void))&t;
11: printf("%d", (*tp)());
12: }
13:
14: return !!argv[argc];
15: }
16: int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_prep_closure_loc; return !p; }
/* end */
--------------------
have_func: checking for ffi_prep_cif_var()... -------------------- yes
LD_LIBRARY_PATH=.:/usr/lib64 "gcc -o conftest -I/usr/include/ruby-3.1.0/x86_64-linux-gnu -I/usr/include/ruby-3.1.0/ruby/backward -I/usr/include/ruby-3.1.0 -I. -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -g -fno-strict-aliasing -fPIC conftest.c -L. -L/usr/lib64 -L. -flto=auto -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -L/usr/lib64/../lib64 -lffi -lffi -lruby3.1 -lffi -lffi -lm -lc"
conftest.c: In function t:
conftest.c:14:57: error: ffi_prep_cif_var undeclared (first use in this function)
14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_prep_cif_var; return !p; }
| ^~~~~~~~~~~~~~~~
conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: /*top*/
4: extern int t(void);
5: int main(int argc, char **argv)
6: {
7: if (argc > 1000000) {
8: int (* volatile tp)(void)=(int (*)(void))&t;
9: printf("%d", (*tp)());
10: }
11:
12: return !!argv[argc];
13: }
14: int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_prep_cif_var; return !p; }
/* end */
LD_LIBRARY_PATH=.:/usr/lib64 "gcc -o conftest -I/usr/include/ruby-3.1.0/x86_64-linux-gnu -I/usr/include/ruby-3.1.0/ruby/backward -I/usr/include/ruby-3.1.0 -I. -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -g -fno-strict-aliasing -fPIC conftest.c -L. -L/usr/lib64 -L. -flto=auto -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -L/usr/lib64/../lib64 -lffi -lffi -lruby3.1 -lffi -lffi -lm -lc"
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: /*top*/
4: extern int t(void);
5: int main(int argc, char **argv)
6: {
7: if (argc > 1000000) {
8: int (* volatile tp)(void)=(int (*)(void))&t;
9: printf("%d", (*tp)());
10: }
11:
12: return !!argv[argc];
13: }
14: extern void ffi_prep_cif_var();
15: int t(void) { ffi_prep_cif_var(); return 0; }
/* end */
--------------------
have_func: checking for ffi_raw_call()... -------------------- yes
LD_LIBRARY_PATH=.:/usr/lib64 "gcc -o conftest -I/usr/include/ruby-3.1.0/x86_64-linux-gnu -I/usr/include/ruby-3.1.0/ruby/backward -I/usr/include/ruby-3.1.0 -I. -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -g -fno-strict-aliasing -fPIC conftest.c -L. -L/usr/lib64 -L. -flto=auto -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -L/usr/lib64/../lib64 -lffi -lffi -lruby3.1 -lffi -lffi -lm -lc"
conftest.c: In function t:
conftest.c:14:57: error: ffi_raw_call undeclared (first use in this function)
14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_raw_call; return !p; }
| ^~~~~~~~~~~~
conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: /*top*/
4: extern int t(void);
5: int main(int argc, char **argv)
6: {
7: if (argc > 1000000) {
8: int (* volatile tp)(void)=(int (*)(void))&t;
9: printf("%d", (*tp)());
10: }
11:
12: return !!argv[argc];
13: }
14: int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_raw_call; return !p; }
/* end */
LD_LIBRARY_PATH=.:/usr/lib64 "gcc -o conftest -I/usr/include/ruby-3.1.0/x86_64-linux-gnu -I/usr/include/ruby-3.1.0/ruby/backward -I/usr/include/ruby-3.1.0 -I. -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -g -fno-strict-aliasing -fPIC conftest.c -L. -L/usr/lib64 -L. -flto=auto -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -L/usr/lib64/../lib64 -lffi -lffi -lruby3.1 -lffi -lffi -lm -lc"
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: /*top*/
4: extern int t(void);
5: int main(int argc, char **argv)
6: {
7: if (argc > 1000000) {
8: int (* volatile tp)(void)=(int (*)(void))&t;
9: printf("%d", (*tp)());
10: }
11:
12: return !!argv[argc];
13: }
14: extern void ffi_raw_call();
15: int t(void) { ffi_raw_call(); return 0; }
/* end */
--------------------
have_func: checking for ffi_prep_raw_closure()... -------------------- yes
LD_LIBRARY_PATH=.:/usr/lib64 "gcc -o conftest -I/usr/include/ruby-3.1.0/x86_64-linux-gnu -I/usr/include/ruby-3.1.0/ruby/backward -I/usr/include/ruby-3.1.0 -I. -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -g -fno-strict-aliasing -fPIC conftest.c -L. -L/usr/lib64 -L. -flto=auto -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -L/usr/lib64/../lib64 -lffi -lffi -lruby3.1 -lffi -lffi -lm -lc"
conftest.c: In function t:
conftest.c:14:57: error: ffi_prep_raw_closure undeclared (first use in this function)
14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_prep_raw_closure; return !p; }
| ^~~~~~~~~~~~~~~~~~~~
conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: /*top*/
4: extern int t(void);
5: int main(int argc, char **argv)
6: {
7: if (argc > 1000000) {
8: int (* volatile tp)(void)=(int (*)(void))&t;
9: printf("%d", (*tp)());
10: }
11:
12: return !!argv[argc];
13: }
14: int t(void) { void ((*volatile p)()); p = (void ((*)()))ffi_prep_raw_closure; return !p; }
/* end */
LD_LIBRARY_PATH=.:/usr/lib64 "gcc -o conftest -I/usr/include/ruby-3.1.0/x86_64-linux-gnu -I/usr/include/ruby-3.1.0/ruby/backward -I/usr/include/ruby-3.1.0 -I. -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -g -fno-strict-aliasing -fPIC conftest.c -L. -L/usr/lib64 -L. -flto=auto -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -L/usr/lib64/../lib64 -lffi -lffi -lruby3.1 -lffi -lffi -lm -lc"
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: /*top*/
4: extern int t(void);
5: int main(int argc, char **argv)
6: {
7: if (argc > 1000000) {
8: int (* volatile tp)(void)=(int (*)(void))&t;
9: printf("%d", (*tp)());
10: }
11:
12: return !!argv[argc];
13: }
14: extern void ffi_prep_raw_closure();
15: int t(void) { ffi_prep_raw_closure(); return 0; }
/* end */
--------------------
block in append_ldflags: checking for whether -pthread is accepted as LDFLAGS... -------------------- yes
LD_LIBRARY_PATH=.:/usr/lib64 "gcc -o conftest -I/usr/include/ruby-3.1.0/x86_64-linux-gnu -I/usr/include/ruby-3.1.0/ruby/backward -I/usr/include/ruby-3.1.0 -I. -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -g -fno-strict-aliasing -fPIC conftest.c -L. -L/usr/lib64 -L. -flto=auto -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -L/usr/lib64/../lib64 -lffi -lffi -lruby3.1 -pthread -lm -lc"
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: int main(int argc, char **argv)
4: {
5: return !!argv[argc];
6: }
/* end */
--------------------
extconf.h is:
/* begin */
1: #ifndef EXTCONF_H
2: #define EXTCONF_H
3: #define HAVE_FFI_PREP_CIF_VAR 1
4: #define HAVE_FFI_RAW_CALL 1
5: #define HAVE_FFI_PREP_RAW_CLOSURE 1
6: #define HAVE_RAW_API 1
7: #endif
/* end */

View File

@ -0,0 +1,3 @@
current directory: /home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/gems/mimemagic-0.4.3/ext/mimemagic
/usr/bin/ruby.ruby3.1 -I/usr/lib64/ruby/3.1.0 -rrubygems /home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/gems/rake-13.0.6/exe/rake RUBYARCHDIR\=/home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/extensions/x86_64-linux/3.1.0/mimemagic-0.4.3 RUBYLIBDIR\=/home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/extensions/x86_64-linux/3.1.0/mimemagic-0.4.3
mkdir -p /home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/extensions/x86_64-linux/3.1.0/mimemagic-0.4.3/mimemagic

View File

@ -0,0 +1,3 @@
class MimeMagic
DATABASE_PATH="/usr/share/mime/packages/freedesktop.org.xml"
end

View File

@ -0,0 +1,16 @@
current directory: /home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/gems/unf_ext-0.0.8/ext/unf_ext
/usr/bin/ruby.ruby3.1 -I /usr/lib64/ruby/3.1.0 -r ./siteconf20220204-28723-f888s4.rb extconf.rb
checking for -lstdc++... yes
creating Makefile
current directory: /home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/gems/unf_ext-0.0.8/ext/unf_ext
make DESTDIR\= clean
current directory: /home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/gems/unf_ext-0.0.8/ext/unf_ext
make DESTDIR\=
compiling unf.cc
linking shared-object unf_ext.so
current directory: /home/stryan/code/stevefolder/vendor/bundle/ruby/3.1.0/gems/unf_ext-0.0.8/ext/unf_ext
make DESTDIR\= install
/usr/bin/install -c -m 0755 unf_ext.so ./.gem.20220204-28723-gq159b

View File

@ -0,0 +1,35 @@
have_library: checking for -lstdc++... -------------------- yes
LD_LIBRARY_PATH=.:/usr/lib64 "gcc -o conftest -I/usr/include/ruby-3.1.0/x86_64-linux-gnu -I/usr/include/ruby-3.1.0/ruby/backward -I/usr/include/ruby-3.1.0 -I. -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -g -fno-strict-aliasing -std=gnu99 -fPIC conftest.c -L. -L/usr/lib64 -L. -flto=auto -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby3.1 -lm -lc"
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: int main(int argc, char **argv)
4: {
5: return !!argv[argc];
6: }
/* end */
LD_LIBRARY_PATH=.:/usr/lib64 "gcc -o conftest -I/usr/include/ruby-3.1.0/x86_64-linux-gnu -I/usr/include/ruby-3.1.0/ruby/backward -I/usr/include/ruby-3.1.0 -I. -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -g -fno-strict-aliasing -std=gnu99 -fPIC conftest.c -L. -L/usr/lib64 -L. -flto=auto -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby3.1 -lstdc++ -lm -lc"
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: /*top*/
4: extern int t(void);
5: int main(int argc, char **argv)
6: {
7: if (argc > 1000000) {
8: int (* volatile tp)(void)=(int (*)(void))&t;
9: printf("%d", (*tp)());
10: }
11:
12: return !!argv[argc];
13: }
14:
15: int t(void) { ; return 0; }
/* end */
--------------------

View File

@ -0,0 +1,246 @@
# Addressable 2.8.0
- fixes ReDoS vulnerability in Addressable::Template#match
- no longer replaces `+` with spaces in queries for non-http(s) schemes
- fixed encoding ipv6 literals
- the `:compacted` flag for `normalized_query` now dedupes parameters
- fix broken `escape_component` alias
- dropping support for Ruby 2.0 and 2.1
- adding Ruby 3.0 compatibility for development tasks
- drop support for `rack-mount` and remove Addressable::Template#generate
- performance improvements
- switch CI/CD to GitHub Actions
# Addressable 2.7.0
- added `:compacted` flag to `normalized_query`
- `heuristic_parse` handles `mailto:` more intuitively
- dropped explicit support for JRuby 9.0.5.0
- compatibility w/ public_suffix 4.x
- performance improvements
# Addressable 2.6.0
- added `tld=` method to allow assignment to the public suffix
- most `heuristic_parse` patterns are now case-insensitive
- `heuristic_parse` handles more `file://` URI variations
- fixes bug in `heuristic_parse` when uri starts with digit
- fixes bug in `request_uri=` with query strings
- fixes template issues with `nil` and `?` operator
- `frozen_string_literal` pragmas added
- minor performance improvements in regexps
- fixes to eliminate warnings
# Addressable 2.5.2
- better support for frozen string literals
- fixed bug w/ uppercase characters in scheme
- IDNA errors w/ emoji URLs
- compatibility w/ public_suffix 3.x
# Addressable 2.5.1
- allow unicode normalization to be disabled for URI Template expansion
- removed duplicate test
# Addressable 2.5.0
- dropping support for Ruby 1.9
- adding support for Ruby 2.4 preview
- add support for public suffixes and tld; first runtime dependency
- hostname escaping should match RFC; underscores in hostnames no longer escaped
- paths beginning with // and missing an authority are now considered invalid
- validation now also takes place after setting a path
- handle backslashes in authority more like a browser for `heuristic_parse`
- unescaped backslashes in host now raise an `InvalidURIError`
- `merge!`, `join!`, `omit!` and `normalize!` don't disable deferred validation
- `heuristic_parse` now trims whitespace before parsing
- host parts longer than 63 bytes will be ignored and not passed to libidn
- normalized values always encoded as UTF-8
# Addressable 2.4.0
- support for 1.8.x dropped
- double quotes in a host now raises an error
- newlines in host will no longer get unescaped during normalization
- stricter handling of bogus scheme values
- stricter handling of encoded port values
- calling `require 'addressable'` will now load both the URI and Template files
- assigning to the `hostname` component with an `IPAddr` object is now supported
- assigning to the `origin` component is now supported
- fixed minor bug where an exception would be thrown for a missing ACE suffix
- better partial expansion of URI templates
# Addressable 2.3.8
- fix warnings
- update dependency gems
- support for 1.8.x officially deprecated
# Addressable 2.3.7
- fix scenario in which invalid URIs don't get an exception until inspected
- handle hostnames with two adjacent periods correctly
- upgrade of RSpec
# Addressable 2.3.6
- normalization drops empty query string
- better handling in template extract for missing values
- template modifier for `'?'` now treated as optional
- fixed issue where character class parameters were modified
- templates can now be tested for equality
- added `:sorted` option to normalization of query strings
- fixed issue with normalization of hosts given in `'example.com.'` form
# Addressable 2.3.5
- added Addressable::URI#empty? method
- Addressable::URI#hostname methods now strip square brackets from IPv6 hosts
- compatibility with Net::HTTP in Ruby 2.0.0
- Addressable::URI#route_from should always give relative URIs
# Addressable 2.3.4
- fixed issue with encoding altering its inputs
- query string normalization now leaves ';' characters alone
- FakeFS is detected before attempting to load unicode tables
- additional testing to ensure frozen objects don't cause problems
# Addressable 2.3.3
- fixed issue with converting common primitives during template expansion
- fixed port encoding issue
- removed a few warnings
- normalize should now ignore %2B in query strings
- the IDNA logic should now be handled by libidn in Ruby 1.9
- no template match should now result in nil instead of an empty MatchData
- added license information to gemspec
# Addressable 2.3.2
- added Addressable::URI#default_port method
- fixed issue with Marshalling Unicode data on Windows
- improved heuristic parsing to better handle IPv4 addresses
# Addressable 2.3.1
- fixed missing unicode data file
# Addressable 2.3.0
- updated Addressable::Template to use RFC 6570, level 4
- fixed compatibility problems with some versions of Ruby
- moved unicode tables into a data file for performance reasons
- removing support for multiple query value notations
# Addressable 2.2.8
- fixed issues with dot segment removal code
- form encoding can now handle multiple values per key
- updated development environment
# Addressable 2.2.7
- fixed issues related to Addressable::URI#query_values=
- the Addressable::URI.parse method is now polymorphic
# Addressable 2.2.6
- changed the way ambiguous paths are handled
- fixed bug with frozen URIs
- https supported in heuristic parsing
# Addressable 2.2.5
- 'parsing' a pre-parsed URI object is now a dup operation
- introduced conditional support for libidn
- fixed normalization issue on ampersands in query strings
- added additional tests around handling of query strings
# Addressable 2.2.4
- added origin support from draft-ietf-websec-origin-00
- resolved issue with attempting to navigate below root
- fixed bug with string splitting in query strings
# Addressable 2.2.3
- added :flat_array notation for query strings
# Addressable 2.2.2
- fixed issue with percent escaping of '+' character in query strings
# Addressable 2.2.1
- added support for application/x-www-form-urlencoded.
# Addressable 2.2.0
- added site methods
- improved documentation
# Addressable 2.1.2
- added HTTP request URI methods
- better handling of Windows file paths
- validation_deferred boolean replaced with defer_validation block
- normalization of percent-encoded paths should now be correct
- fixed issue with constructing URIs with relative paths
- fixed warnings
# Addressable 2.1.1
- more type checking changes
- fixed issue with unicode normalization
- added method to find template defaults
- symbolic keys are now allowed in template mappings
- numeric values and symbolic values are now allowed in template mappings
# Addressable 2.1.0
- refactored URI template support out into its own class
- removed extract method due to being useless and unreliable
- removed Addressable::URI.expand_template
- removed Addressable::URI#extract_mapping
- added partial template expansion
- fixed minor bugs in the parse and heuristic_parse methods
- fixed incompatibility with Ruby 1.9.1
- fixed bottleneck in Addressable::URI#hash and Addressable::URI#to_s
- fixed unicode normalization exception
- updated query_values methods to better handle subscript notation
- worked around issue with freezing URIs
- improved specs
# Addressable 2.0.2
- fixed issue with URI template expansion
- fixed issue with percent escaping characters 0-15
# Addressable 2.0.1
- fixed issue with query string assignment
- fixed issue with improperly encoded components
# Addressable 2.0.0
- the initialize method now takes an options hash as its only parameter
- added query_values method to URI class
- completely replaced IDNA implementation with pure Ruby
- renamed Addressable::ADDRESSABLE_VERSION to Addressable::VERSION
- completely reworked the Rakefile
- changed the behavior of the port method significantly
- Addressable::URI.encode_segment, Addressable::URI.unencode_segment renamed
- documentation is now in YARD format
- more rigorous type checking
- to_str method implemented, implicit conversion to Strings now allowed
- Addressable::URI#omit method added, Addressable::URI#merge method replaced
- updated URI Template code to match v 03 of the draft spec
- added a bunch of new specifications
# Addressable 1.0.4
- switched to using RSpec's pending system for specs that rely on IDN
- fixed issue with creating URIs with paths that are not prefixed with '/'
# Addressable 1.0.3
- implemented a hash method
# Addressable 1.0.2
- fixed minor bug with the extract_mapping method
# Addressable 1.0.1
- fixed minor bug with the extract_mapping method
# Addressable 1.0.0
- heuristic parse method added
- parsing is slightly more strict
- replaced to_h with to_hash
- fixed routing methods
- improved specifications
- improved heckle rake task
- no surviving heckle mutations
# Addressable 0.1.2
- improved normalization
- fixed bug in joining algorithm
- updated specifications
# Addressable 0.1.1
- updated documentation
- added URI Template variable extraction
# Addressable 0.1.0
- initial release
- implementation based on RFC 3986, 3987
- support for IRIs via libidn
- support for the URI Template draft spec

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
source 'https://rubygems.org'
gemspec(path: __FILE__ == "(eval)" ? ".." : ".")
group :test do
gem 'rspec', '~> 3.8'
gem 'rspec-its', '~> 1.3'
end
group :coverage do
gem "coveralls", "> 0.7", require: false, platforms: :mri
gem "simplecov", require: false
end
group :development do
gem 'launchy', '~> 2.4', '>= 2.4.3'
gem 'redcarpet', :platform => :mri_19
gem 'yard'
end
group :test, :development do
gem 'memory_profiler'
gem "rake", ">= 12.3.3"
end
gem "idn-ruby", platform: :mri

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,121 @@
# Addressable
<dl>
<dt>Homepage</dt><dd><a href="https://github.com/sporkmonger/addressable">github.com/sporkmonger/addressable</a></dd>
<dt>Author</dt><dd><a href="mailto:bob@sporkmonger.com">Bob Aman</a></dd>
<dt>Copyright</dt><dd>Copyright © Bob Aman</dd>
<dt>License</dt><dd>Apache 2.0</dd>
</dl>
[![Gem Version](https://img.shields.io/gem/dt/addressable.svg)][gem]
[![Build Status](https://github.com/sporkmonger/addressable/workflows/CI/badge.svg)][actions]
[![Test Coverage Status](https://img.shields.io/coveralls/sporkmonger/addressable.svg)][coveralls]
[![Documentation Coverage Status](https://inch-ci.org/github/sporkmonger/addressable.svg?branch=master)][inch]
[gem]: https://rubygems.org/gems/addressable
[actions]: https://github.com/sporkmonger/addressable/actions
[coveralls]: https://coveralls.io/r/sporkmonger/addressable
[inch]: https://inch-ci.org/github/sporkmonger/addressable
# Description
Addressable is an alternative implementation to the URI implementation
that is part of Ruby's standard library. It is flexible, offers heuristic
parsing, and additionally provides extensive support for IRIs and URI templates.
Addressable closely conforms to RFC 3986, RFC 3987, and RFC 6570 (level 4).
# Reference
- {Addressable::URI}
- {Addressable::Template}
# Example usage
```ruby
require "addressable/uri"
uri = Addressable::URI.parse("http://example.com/path/to/resource/")
uri.scheme
#=> "http"
uri.host
#=> "example.com"
uri.path
#=> "/path/to/resource/"
uri = Addressable::URI.parse("http://www.詹姆斯.com/")
uri.normalize
#=> #<Addressable::URI:0xc9a4c8 URI:http://www.xn--8ws00zhy3a.com/>
```
# URI Templates
For more details, see [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570.txt).
```ruby
require "addressable/template"
template = Addressable::Template.new("http://example.com/{?query*}")
template.expand({
"query" => {
'foo' => 'bar',
'color' => 'red'
}
})
#=> #<Addressable::URI:0xc9d95c URI:http://example.com/?foo=bar&color=red>
template = Addressable::Template.new("http://example.com/{?one,two,three}")
template.partial_expand({"one" => "1", "three" => 3}).pattern
#=> "http://example.com/?one=1{&two}&three=3"
template = Addressable::Template.new(
"http://{host}{/segments*}/{?one,two,bogus}{#fragment}"
)
uri = Addressable::URI.parse(
"http://example.com/a/b/c/?one=1&two=2#foo"
)
template.extract(uri)
#=>
# {
# "host" => "example.com",
# "segments" => ["a", "b", "c"],
# "one" => "1",
# "two" => "2",
# "fragment" => "foo"
# }
```
# Install
```console
$ gem install addressable
```
You may optionally turn on native IDN support by installing libidn and the
idn gem:
```console
$ sudo apt-get install libidn11-dev # Debian/Ubuntu
$ brew install libidn # OS X
$ gem install idn-ruby
```
# Semantic Versioning
This project uses [Semantic Versioning](https://semver.org/). You can (and should) specify your
dependency using a pessimistic version constraint covering the major and minor
values:
```ruby
spec.add_dependency 'addressable', '~> 2.7'
```
If you need a specific bug fix, you can also specify minimum tiny versions
without preventing updates to the latest minor release:
```ruby
spec.add_dependency 'addressable', '~> 2.3', '>= 2.3.7'
```

View File

@ -0,0 +1,34 @@
# frozen_string_literal: true
require 'rubygems'
require 'rake'
require File.join(File.dirname(__FILE__), 'lib', 'addressable', 'version')
PKG_DISPLAY_NAME = 'Addressable'
PKG_NAME = PKG_DISPLAY_NAME.downcase
PKG_VERSION = Addressable::VERSION::STRING
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
RELEASE_NAME = "REL #{PKG_VERSION}"
PKG_SUMMARY = "URI Implementation"
PKG_DESCRIPTION = <<-TEXT
Addressable is an alternative implementation to the URI implementation that is
part of Ruby's standard library. It is flexible, offers heuristic parsing, and
additionally provides extensive support for IRIs and URI templates.
TEXT
PKG_FILES = FileList[
"lib/**/*", "spec/**/*", "vendor/**/*", "data/**/*",
"tasks/**/*",
"[A-Z]*", "Rakefile"
].exclude(/pkg/).exclude(/database\.yml/).
exclude(/Gemfile\.lock/).exclude(/[_\.]git$/)
task :default => "spec"
WINDOWS = (RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/) rescue false
SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])
Dir['tasks/**/*.rake'].each { |rake| load rake }

View File

@ -0,0 +1,37 @@
# -*- encoding: utf-8 -*-
# stub: addressable 2.8.0 ruby lib
Gem::Specification.new do |s|
s.name = "addressable".freeze
s.version = "2.8.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
s.require_paths = ["lib".freeze]
s.authors = ["Bob Aman".freeze]
s.date = "2021-07-03"
s.description = "Addressable is an alternative implementation to the URI implementation that is\npart of Ruby's standard library. It is flexible, offers heuristic parsing, and\nadditionally provides extensive support for IRIs and URI templates.\n".freeze
s.email = "bob@sporkmonger.com".freeze
s.extra_rdoc_files = ["README.md".freeze]
s.files = ["CHANGELOG.md".freeze, "Gemfile".freeze, "LICENSE.txt".freeze, "README.md".freeze, "Rakefile".freeze, "addressable.gemspec".freeze, "data/unicode.data".freeze, "lib/addressable.rb".freeze, "lib/addressable/idna.rb".freeze, "lib/addressable/idna/native.rb".freeze, "lib/addressable/idna/pure.rb".freeze, "lib/addressable/template.rb".freeze, "lib/addressable/uri.rb".freeze, "lib/addressable/version.rb".freeze, "spec/addressable/idna_spec.rb".freeze, "spec/addressable/net_http_compat_spec.rb".freeze, "spec/addressable/security_spec.rb".freeze, "spec/addressable/template_spec.rb".freeze, "spec/addressable/uri_spec.rb".freeze, "spec/spec_helper.rb".freeze, "tasks/clobber.rake".freeze, "tasks/gem.rake".freeze, "tasks/git.rake".freeze, "tasks/metrics.rake".freeze, "tasks/profile.rake".freeze, "tasks/rspec.rake".freeze, "tasks/yard.rake".freeze]
s.homepage = "https://github.com/sporkmonger/addressable".freeze
s.licenses = ["Apache-2.0".freeze]
s.rdoc_options = ["--main".freeze, "README.md".freeze]
s.required_ruby_version = Gem::Requirement.new(">= 2.0".freeze)
s.rubygems_version = "3.0.3".freeze
s.summary = "URI Implementation".freeze
if s.respond_to? :specification_version then
s.specification_version = 4
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<public_suffix>.freeze, [">= 2.0.2", "< 5.0"])
s.add_development_dependency(%q<bundler>.freeze, [">= 1.0", "< 3.0"])
else
s.add_dependency(%q<public_suffix>.freeze, [">= 2.0.2", "< 5.0"])
s.add_dependency(%q<bundler>.freeze, [">= 1.0", "< 3.0"])
end
else
s.add_dependency(%q<public_suffix>.freeze, [">= 2.0.2", "< 5.0"])
s.add_dependency(%q<bundler>.freeze, [">= 1.0", "< 3.0"])
end
end

Binary file not shown.

View File

@ -0,0 +1,4 @@
# frozen_string_literal: true
require 'addressable/uri'
require 'addressable/template'

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
# encoding:utf-8
#--
# Copyright (C) Bob Aman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#++
begin
require "addressable/idna/native"
rescue LoadError
# libidn or the idn gem was not available, fall back on a pure-Ruby
# implementation...
require "addressable/idna/pure"
end

View File

@ -0,0 +1,61 @@
# frozen_string_literal: true
# encoding:utf-8
#--
# Copyright (C) Bob Aman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#++
require "idn"
module Addressable
module IDNA
def self.punycode_encode(value)
IDN::Punycode.encode(value.to_s)
end
def self.punycode_decode(value)
IDN::Punycode.decode(value.to_s)
end
def self.unicode_normalize_kc(value)
IDN::Stringprep.nfkc_normalize(value.to_s)
end
def self.to_ascii(value)
value.to_s.split('.', -1).map do |segment|
if segment.size > 0 && segment.size < 64
IDN::Idna.toASCII(segment, IDN::Idna::ALLOW_UNASSIGNED)
elsif segment.size >= 64
segment
else
''
end
end.join('.')
end
def self.to_unicode(value)
value.to_s.split('.', -1).map do |segment|
if segment.size > 0 && segment.size < 64
IDN::Idna.toUnicode(segment, IDN::Idna::ALLOW_UNASSIGNED)
elsif segment.size >= 64
segment
else
''
end
end.join('.')
end
end
end

View File

@ -0,0 +1,678 @@
# frozen_string_literal: true
# encoding:utf-8
#--
# Copyright (C) Bob Aman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#++
module Addressable
module IDNA
# This module is loosely based on idn_actionmailer by Mick Staugaard,
# the unicode library by Yoshida Masato, and the punycode implementation
# by Kazuhiro Nishiyama. Most of the code was copied verbatim, but
# some reformatting was done, and some translation from C was done.
#
# Without their code to work from as a base, we'd all still be relying
# on the presence of libidn. Which nobody ever seems to have installed.
#
# Original sources:
# http://github.com/staugaard/idn_actionmailer
# http://www.yoshidam.net/Ruby.html#unicode
# http://rubyforge.org/frs/?group_id=2550
UNICODE_TABLE = File.expand_path(
File.join(File.dirname(__FILE__), '../../..', 'data/unicode.data')
)
ACE_PREFIX = "xn--"
UTF8_REGEX = /\A(?:
[\x09\x0A\x0D\x20-\x7E] # ASCII
| [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
)*\z/mnx
UTF8_REGEX_MULTIBYTE = /(?:
[\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
)/mnx
# :startdoc:
# Converts from a Unicode internationalized domain name to an ASCII
# domain name as described in RFC 3490.
def self.to_ascii(input)
input = input.to_s unless input.is_a?(String)
input = input.dup
if input.respond_to?(:force_encoding)
input.force_encoding(Encoding::ASCII_8BIT)
end
if input =~ UTF8_REGEX && input =~ UTF8_REGEX_MULTIBYTE
parts = unicode_downcase(input).split('.')
parts.map! do |part|
if part.respond_to?(:force_encoding)
part.force_encoding(Encoding::ASCII_8BIT)
end
if part =~ UTF8_REGEX && part =~ UTF8_REGEX_MULTIBYTE
ACE_PREFIX + punycode_encode(unicode_normalize_kc(part))
else
part
end
end
parts.join('.')
else
input
end
end
# Converts from an ASCII domain name to a Unicode internationalized
# domain name as described in RFC 3490.
def self.to_unicode(input)
input = input.to_s unless input.is_a?(String)
parts = input.split('.')
parts.map! do |part|
if part =~ /^#{ACE_PREFIX}(.+)/
begin
punycode_decode(part[/^#{ACE_PREFIX}(.+)/, 1])
rescue Addressable::IDNA::PunycodeBadInput
# toUnicode is explicitly defined as never-fails by the spec
part
end
else
part
end
end
output = parts.join('.')
if output.respond_to?(:force_encoding)
output.force_encoding(Encoding::UTF_8)
end
output
end
# Unicode normalization form KC.
def self.unicode_normalize_kc(input)
input = input.to_s unless input.is_a?(String)
unpacked = input.unpack("U*")
unpacked =
unicode_compose(unicode_sort_canonical(unicode_decompose(unpacked)))
return unpacked.pack("U*")
end
##
# Unicode aware downcase method.
#
# @api private
# @param [String] input
# The input string.
# @return [String] The downcased result.
def self.unicode_downcase(input)
input = input.to_s unless input.is_a?(String)
unpacked = input.unpack("U*")
unpacked.map! { |codepoint| lookup_unicode_lowercase(codepoint) }
return unpacked.pack("U*")
end
private_class_method :unicode_downcase
def self.unicode_compose(unpacked)
unpacked_result = []
length = unpacked.length
return unpacked if length == 0
starter = unpacked[0]
starter_cc = lookup_unicode_combining_class(starter)
starter_cc = 256 if starter_cc != 0
for i in 1...length
ch = unpacked[i]
if (starter_cc == 0 &&
(composite = unicode_compose_pair(starter, ch)) != nil)
starter = composite
else
unpacked_result << starter
starter = ch
end
end
unpacked_result << starter
return unpacked_result
end
private_class_method :unicode_compose
def self.unicode_compose_pair(ch_one, ch_two)
if ch_one >= HANGUL_LBASE && ch_one < HANGUL_LBASE + HANGUL_LCOUNT &&
ch_two >= HANGUL_VBASE && ch_two < HANGUL_VBASE + HANGUL_VCOUNT
# Hangul L + V
return HANGUL_SBASE + (
(ch_one - HANGUL_LBASE) * HANGUL_VCOUNT + (ch_two - HANGUL_VBASE)
) * HANGUL_TCOUNT
elsif ch_one >= HANGUL_SBASE &&
ch_one < HANGUL_SBASE + HANGUL_SCOUNT &&
(ch_one - HANGUL_SBASE) % HANGUL_TCOUNT == 0 &&
ch_two >= HANGUL_TBASE && ch_two < HANGUL_TBASE + HANGUL_TCOUNT
# Hangul LV + T
return ch_one + (ch_two - HANGUL_TBASE)
end
p = []
ucs4_to_utf8(ch_one, p)
ucs4_to_utf8(ch_two, p)
return lookup_unicode_composition(p)
end
private_class_method :unicode_compose_pair
def self.ucs4_to_utf8(char, buffer)
if char < 128
buffer << char
elsif char < 2048
buffer << (char >> 6 | 192)
buffer << (char & 63 | 128)
elsif char < 0x10000
buffer << (char >> 12 | 224)
buffer << (char >> 6 & 63 | 128)
buffer << (char & 63 | 128)
elsif char < 0x200000
buffer << (char >> 18 | 240)
buffer << (char >> 12 & 63 | 128)
buffer << (char >> 6 & 63 | 128)
buffer << (char & 63 | 128)
elsif char < 0x4000000
buffer << (char >> 24 | 248)
buffer << (char >> 18 & 63 | 128)
buffer << (char >> 12 & 63 | 128)
buffer << (char >> 6 & 63 | 128)
buffer << (char & 63 | 128)
elsif char < 0x80000000
buffer << (char >> 30 | 252)
buffer << (char >> 24 & 63 | 128)
buffer << (char >> 18 & 63 | 128)
buffer << (char >> 12 & 63 | 128)
buffer << (char >> 6 & 63 | 128)
buffer << (char & 63 | 128)
end
end
private_class_method :ucs4_to_utf8
def self.unicode_sort_canonical(unpacked)
unpacked = unpacked.dup
i = 1
length = unpacked.length
return unpacked if length < 2
while i < length
last = unpacked[i-1]
ch = unpacked[i]
last_cc = lookup_unicode_combining_class(last)
cc = lookup_unicode_combining_class(ch)
if cc != 0 && last_cc != 0 && last_cc > cc
unpacked[i] = last
unpacked[i-1] = ch
i -= 1 if i > 1
else
i += 1
end
end
return unpacked
end
private_class_method :unicode_sort_canonical
def self.unicode_decompose(unpacked)
unpacked_result = []
for cp in unpacked
if cp >= HANGUL_SBASE && cp < HANGUL_SBASE + HANGUL_SCOUNT
l, v, t = unicode_decompose_hangul(cp)
unpacked_result << l
unpacked_result << v if v
unpacked_result << t if t
else
dc = lookup_unicode_compatibility(cp)
unless dc
unpacked_result << cp
else
unpacked_result.concat(unicode_decompose(dc.unpack("U*")))
end
end
end
return unpacked_result
end
private_class_method :unicode_decompose
def self.unicode_decompose_hangul(codepoint)
sindex = codepoint - HANGUL_SBASE;
if sindex < 0 || sindex >= HANGUL_SCOUNT
l = codepoint
v = t = nil
return l, v, t
end
l = HANGUL_LBASE + sindex / HANGUL_NCOUNT
v = HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT
t = HANGUL_TBASE + sindex % HANGUL_TCOUNT
if t == HANGUL_TBASE
t = nil
end
return l, v, t
end
private_class_method :unicode_decompose_hangul
def self.lookup_unicode_combining_class(codepoint)
codepoint_data = UNICODE_DATA[codepoint]
(codepoint_data ?
(codepoint_data[UNICODE_DATA_COMBINING_CLASS] || 0) :
0)
end
private_class_method :lookup_unicode_combining_class
def self.lookup_unicode_compatibility(codepoint)
codepoint_data = UNICODE_DATA[codepoint]
(codepoint_data ?
codepoint_data[UNICODE_DATA_COMPATIBILITY] : nil)
end
private_class_method :lookup_unicode_compatibility
def self.lookup_unicode_lowercase(codepoint)
codepoint_data = UNICODE_DATA[codepoint]
(codepoint_data ?
(codepoint_data[UNICODE_DATA_LOWERCASE] || codepoint) :
codepoint)
end
private_class_method :lookup_unicode_lowercase
def self.lookup_unicode_composition(unpacked)
return COMPOSITION_TABLE[unpacked]
end
private_class_method :lookup_unicode_composition
HANGUL_SBASE = 0xac00
HANGUL_LBASE = 0x1100
HANGUL_LCOUNT = 19
HANGUL_VBASE = 0x1161
HANGUL_VCOUNT = 21
HANGUL_TBASE = 0x11a7
HANGUL_TCOUNT = 28
HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT # 588
HANGUL_SCOUNT = HANGUL_LCOUNT * HANGUL_NCOUNT # 11172
UNICODE_DATA_COMBINING_CLASS = 0
UNICODE_DATA_EXCLUSION = 1
UNICODE_DATA_CANONICAL = 2
UNICODE_DATA_COMPATIBILITY = 3
UNICODE_DATA_UPPERCASE = 4
UNICODE_DATA_LOWERCASE = 5
UNICODE_DATA_TITLECASE = 6
begin
if defined?(FakeFS)
fakefs_state = FakeFS.activated?
FakeFS.deactivate!
end
# This is a sparse Unicode table. Codepoints without entries are
# assumed to have the value: [0, 0, nil, nil, nil, nil, nil]
UNICODE_DATA = File.open(UNICODE_TABLE, "rb") do |file|
Marshal.load(file.read)
end
ensure
if defined?(FakeFS)
FakeFS.activate! if fakefs_state
end
end
COMPOSITION_TABLE = {}
UNICODE_DATA.each do |codepoint, data|
canonical = data[UNICODE_DATA_CANONICAL]
exclusion = data[UNICODE_DATA_EXCLUSION]
if canonical && exclusion == 0
COMPOSITION_TABLE[canonical.unpack("C*")] = codepoint
end
end
UNICODE_MAX_LENGTH = 256
ACE_MAX_LENGTH = 256
PUNYCODE_BASE = 36
PUNYCODE_TMIN = 1
PUNYCODE_TMAX = 26
PUNYCODE_SKEW = 38
PUNYCODE_DAMP = 700
PUNYCODE_INITIAL_BIAS = 72
PUNYCODE_INITIAL_N = 0x80
PUNYCODE_DELIMITER = 0x2D
PUNYCODE_MAXINT = 1 << 64
PUNYCODE_PRINT_ASCII =
"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +
"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +
" !\"\#$%&'()*+,-./" +
"0123456789:;<=>?" +
"@ABCDEFGHIJKLMNO" +
"PQRSTUVWXYZ[\\]^_" +
"`abcdefghijklmno" +
"pqrstuvwxyz{|}~\n"
# Input is invalid.
class PunycodeBadInput < StandardError; end
# Output would exceed the space provided.
class PunycodeBigOutput < StandardError; end
# Input needs wider integers to process.
class PunycodeOverflow < StandardError; end
def self.punycode_encode(unicode)
unicode = unicode.to_s unless unicode.is_a?(String)
input = unicode.unpack("U*")
output = [0] * (ACE_MAX_LENGTH + 1)
input_length = input.size
output_length = [ACE_MAX_LENGTH]
# Initialize the state
n = PUNYCODE_INITIAL_N
delta = out = 0
max_out = output_length[0]
bias = PUNYCODE_INITIAL_BIAS
# Handle the basic code points:
input_length.times do |j|
if punycode_basic?(input[j])
if max_out - out < 2
raise PunycodeBigOutput,
"Output would exceed the space provided."
end
output[out] = input[j]
out += 1
end
end
h = b = out
# h is the number of code points that have been handled, b is the
# number of basic code points, and out is the number of characters
# that have been output.
if b > 0
output[out] = PUNYCODE_DELIMITER
out += 1
end
# Main encoding loop:
while h < input_length
# All non-basic code points < n have been
# handled already. Find the next larger one:
m = PUNYCODE_MAXINT
input_length.times do |j|
m = input[j] if (n...m) === input[j]
end
# Increase delta enough to advance the decoder's
# <n,i> state to <m,0>, but guard against overflow:
if m - n > (PUNYCODE_MAXINT - delta) / (h + 1)
raise PunycodeOverflow, "Input needs wider integers to process."
end
delta += (m - n) * (h + 1)
n = m
input_length.times do |j|
# Punycode does not need to check whether input[j] is basic:
if input[j] < n
delta += 1
if delta == 0
raise PunycodeOverflow,
"Input needs wider integers to process."
end
end
if input[j] == n
# Represent delta as a generalized variable-length integer:
q = delta; k = PUNYCODE_BASE
while true
if out >= max_out
raise PunycodeBigOutput,
"Output would exceed the space provided."
end
t = (
if k <= bias
PUNYCODE_TMIN
elsif k >= bias + PUNYCODE_TMAX
PUNYCODE_TMAX
else
k - bias
end
)
break if q < t
output[out] =
punycode_encode_digit(t + (q - t) % (PUNYCODE_BASE - t))
out += 1
q = (q - t) / (PUNYCODE_BASE - t)
k += PUNYCODE_BASE
end
output[out] = punycode_encode_digit(q)
out += 1
bias = punycode_adapt(delta, h + 1, h == b)
delta = 0
h += 1
end
end
delta += 1
n += 1
end
output_length[0] = out
outlen = out
outlen.times do |j|
c = output[j]
unless c >= 0 && c <= 127
raise StandardError, "Invalid output char."
end
unless PUNYCODE_PRINT_ASCII[c]
raise PunycodeBadInput, "Input is invalid."
end
end
output[0..outlen].map { |x| x.chr }.join("").sub(/\0+\z/, "")
end
private_class_method :punycode_encode
def self.punycode_decode(punycode)
input = []
output = []
if ACE_MAX_LENGTH * 2 < punycode.size
raise PunycodeBigOutput, "Output would exceed the space provided."
end
punycode.each_byte do |c|
unless c >= 0 && c <= 127
raise PunycodeBadInput, "Input is invalid."
end
input.push(c)
end
input_length = input.length
output_length = [UNICODE_MAX_LENGTH]
# Initialize the state
n = PUNYCODE_INITIAL_N
out = i = 0
max_out = output_length[0]
bias = PUNYCODE_INITIAL_BIAS
# Handle the basic code points: Let b be the number of input code
# points before the last delimiter, or 0 if there is none, then
# copy the first b code points to the output.
b = 0
input_length.times do |j|
b = j if punycode_delimiter?(input[j])
end
if b > max_out
raise PunycodeBigOutput, "Output would exceed the space provided."
end
b.times do |j|
unless punycode_basic?(input[j])
raise PunycodeBadInput, "Input is invalid."
end
output[out] = input[j]
out+=1
end
# Main decoding loop: Start just after the last delimiter if any
# basic code points were copied; start at the beginning otherwise.
in_ = b > 0 ? b + 1 : 0
while in_ < input_length
# in_ is the index of the next character to be consumed, and
# out is the number of code points in the output array.
# Decode a generalized variable-length integer into delta,
# which gets added to i. The overflow checking is easier
# if we increase i as we go, then subtract off its starting
# value at the end to obtain delta.
oldi = i; w = 1; k = PUNYCODE_BASE
while true
if in_ >= input_length
raise PunycodeBadInput, "Input is invalid."
end
digit = punycode_decode_digit(input[in_])
in_+=1
if digit >= PUNYCODE_BASE
raise PunycodeBadInput, "Input is invalid."
end
if digit > (PUNYCODE_MAXINT - i) / w
raise PunycodeOverflow, "Input needs wider integers to process."
end
i += digit * w
t = (
if k <= bias
PUNYCODE_TMIN
elsif k >= bias + PUNYCODE_TMAX
PUNYCODE_TMAX
else
k - bias
end
)
break if digit < t
if w > PUNYCODE_MAXINT / (PUNYCODE_BASE - t)
raise PunycodeOverflow, "Input needs wider integers to process."
end
w *= PUNYCODE_BASE - t
k += PUNYCODE_BASE
end
bias = punycode_adapt(i - oldi, out + 1, oldi == 0)
# I was supposed to wrap around from out + 1 to 0,
# incrementing n each time, so we'll fix that now:
if i / (out + 1) > PUNYCODE_MAXINT - n
raise PunycodeOverflow, "Input needs wider integers to process."
end
n += i / (out + 1)
i %= out + 1
# Insert n at position i of the output:
# not needed for Punycode:
# raise PUNYCODE_INVALID_INPUT if decode_digit(n) <= base
if out >= max_out
raise PunycodeBigOutput, "Output would exceed the space provided."
end
#memmove(output + i + 1, output + i, (out - i) * sizeof *output)
output[i + 1, out - i] = output[i, out - i]
output[i] = n
i += 1
out += 1
end
output_length[0] = out
output.pack("U*")
end
private_class_method :punycode_decode
def self.punycode_basic?(codepoint)
codepoint < 0x80
end
private_class_method :punycode_basic?
def self.punycode_delimiter?(codepoint)
codepoint == PUNYCODE_DELIMITER
end
private_class_method :punycode_delimiter?
def self.punycode_encode_digit(d)
d + 22 + 75 * ((d < 26) ? 1 : 0)
end
private_class_method :punycode_encode_digit
# Returns the numeric value of a basic codepoint
# (for use in representing integers) in the range 0 to
# base - 1, or PUNYCODE_BASE if codepoint does not represent a value.
def self.punycode_decode_digit(codepoint)
if codepoint - 48 < 10
codepoint - 22
elsif codepoint - 65 < 26
codepoint - 65
elsif codepoint - 97 < 26
codepoint - 97
else
PUNYCODE_BASE
end
end
private_class_method :punycode_decode_digit
# Bias adaptation method
def self.punycode_adapt(delta, numpoints, firsttime)
delta = firsttime ? delta / PUNYCODE_DAMP : delta >> 1
# delta >> 1 is a faster way of doing delta / 2
delta += delta / numpoints
difference = PUNYCODE_BASE - PUNYCODE_TMIN
k = 0
while delta > (difference * PUNYCODE_TMAX) / 2
delta /= difference
k += PUNYCODE_BASE
end
k + (difference + 1) * delta / (delta + PUNYCODE_SKEW)
end
private_class_method :punycode_adapt
end
# :startdoc:
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
# encoding:utf-8
#--
# Copyright (C) Bob Aman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#++
# Used to prevent the class/module from being loaded more than once
if !defined?(Addressable::VERSION)
module Addressable
module VERSION
MAJOR = 2
MINOR = 8
TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
end
end
end

View File

@ -0,0 +1,302 @@
# frozen_string_literal: true
# coding: utf-8
# Copyright (C) Bob Aman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require "spec_helper"
# Have to use RubyGems to load the idn gem.
require "rubygems"
require "addressable/idna"
shared_examples_for "converting from unicode to ASCII" do
it "should convert 'www.google.com' correctly" do
expect(Addressable::IDNA.to_ascii("www.google.com")).to eq("www.google.com")
end
long = 'AcinusFallumTrompetumNullunCreditumVisumEstAtCuadLongumEtCefallum.com'
it "should convert '#{long}' correctly" do
expect(Addressable::IDNA.to_ascii(long)).to eq(long)
end
it "should convert 'www.詹姆斯.com' correctly" do
expect(Addressable::IDNA.to_ascii(
"www.詹姆斯.com"
)).to eq("www.xn--8ws00zhy3a.com")
end
it "should convert 'www.Iñtërnâtiônàlizætiøn.com' correctly" do
"www.Iñtërnâtiônàlizætiøn.com"
expect(Addressable::IDNA.to_ascii(
"www.I\xC3\xB1t\xC3\xABrn\xC3\xA2ti\xC3\xB4" +
"n\xC3\xA0liz\xC3\xA6ti\xC3\xB8n.com"
)).to eq("www.xn--itrntinliztin-vdb0a5exd8ewcye.com")
end
it "should convert 'www.Iñtërnâtiônàlizætiøn.com' correctly" do
expect(Addressable::IDNA.to_ascii(
"www.In\xCC\x83te\xCC\x88rna\xCC\x82tio\xCC\x82n" +
"a\xCC\x80liz\xC3\xA6ti\xC3\xB8n.com"
)).to eq("www.xn--itrntinliztin-vdb0a5exd8ewcye.com")
end
it "should convert " +
"'www.ほんとうにながいわけのわからないどめいんめいのらべるまだながくしないとたりない.w3.mag.keio.ac.jp' " +
"correctly" do
expect(Addressable::IDNA.to_ascii(
"www.\343\201\273\343\202\223\343\201\250\343\201\206\343\201\253\343" +
"\201\252\343\201\214\343\201\204\343\202\217\343\201\221\343\201\256" +
"\343\202\217\343\201\213\343\202\211\343\201\252\343\201\204\343\201" +
"\251\343\202\201\343\201\204\343\202\223\343\202\201\343\201\204\343" +
"\201\256\343\202\211\343\201\271\343\202\213\343\201\276\343\201\240" +
"\343\201\252\343\201\214\343\201\217\343\201\227\343\201\252\343\201" +
"\204\343\201\250\343\201\237\343\202\212\343\201\252\343\201\204." +
"w3.mag.keio.ac.jp"
)).to eq(
"www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3" +
"fg11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp"
)
end
it "should convert " +
"'www.ほんとうにながいわけのわからないどめいんめいのらべるまだながくしないとたりない.w3.mag.keio.ac.jp' " +
"correctly" do
expect(Addressable::IDNA.to_ascii(
"www.\343\201\273\343\202\223\343\201\250\343\201\206\343\201\253\343" +
"\201\252\343\201\213\343\202\231\343\201\204\343\202\217\343\201\221" +
"\343\201\256\343\202\217\343\201\213\343\202\211\343\201\252\343\201" +
"\204\343\201\250\343\202\231\343\202\201\343\201\204\343\202\223\343" +
"\202\201\343\201\204\343\201\256\343\202\211\343\201\270\343\202\231" +
"\343\202\213\343\201\276\343\201\237\343\202\231\343\201\252\343\201" +
"\213\343\202\231\343\201\217\343\201\227\343\201\252\343\201\204\343" +
"\201\250\343\201\237\343\202\212\343\201\252\343\201\204." +
"w3.mag.keio.ac.jp"
)).to eq(
"www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3" +
"fg11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp"
)
end
it "should convert '点心和烤鸭.w3.mag.keio.ac.jp' correctly" do
expect(Addressable::IDNA.to_ascii(
"点心和烤鸭.w3.mag.keio.ac.jp"
)).to eq("xn--0trv4xfvn8el34t.w3.mag.keio.ac.jp")
end
it "should convert '가각갂갃간갅갆갇갈갉힢힣.com' correctly" do
expect(Addressable::IDNA.to_ascii(
"가각갂갃간갅갆갇갈갉힢힣.com"
)).to eq("xn--o39acdefghijk5883jma.com")
end
it "should convert " +
"'\347\242\274\346\250\231\346\272\226\350" +
"\220\254\345\234\213\347\242\274.com' correctly" do
expect(Addressable::IDNA.to_ascii(
"\347\242\274\346\250\231\346\272\226\350" +
"\220\254\345\234\213\347\242\274.com"
)).to eq("xn--9cs565brid46mda086o.com")
end
it "should convert 'リ宠퐱〹.com' correctly" do
expect(Addressable::IDNA.to_ascii(
"\357\276\230\345\256\240\355\220\261\343\200\271.com"
)).to eq("xn--eek174hoxfpr4k.com")
end
it "should convert 'リ宠퐱卄.com' correctly" do
expect(Addressable::IDNA.to_ascii(
"\343\203\252\345\256\240\355\220\261\345\215\204.com"
)).to eq("xn--eek174hoxfpr4k.com")
end
it "should convert 'ᆵ' correctly" do
expect(Addressable::IDNA.to_ascii(
"\341\206\265"
)).to eq("xn--4ud")
end
it "should convert 'ᆵ' correctly" do
expect(Addressable::IDNA.to_ascii(
"\357\276\257"
)).to eq("xn--4ud")
end
it "should convert '🌹🌹🌹.ws' correctly" do
expect(Addressable::IDNA.to_ascii(
"\360\237\214\271\360\237\214\271\360\237\214\271.ws"
)).to eq("xn--2h8haa.ws")
end
it "should handle two adjacent '.'s correctly" do
expect(Addressable::IDNA.to_ascii(
"example..host"
)).to eq("example..host")
end
end
shared_examples_for "converting from ASCII to unicode" do
long = 'AcinusFallumTrompetumNullunCreditumVisumEstAtCuadLongumEtCefallum.com'
it "should convert '#{long}' correctly" do
expect(Addressable::IDNA.to_unicode(long)).to eq(long)
end
it "should return the identity conversion when punycode decode fails" do
expect(Addressable::IDNA.to_unicode("xn--zckp1cyg1.sblo.jp")).to eq(
"xn--zckp1cyg1.sblo.jp")
end
it "should return the identity conversion when the ACE prefix has no suffix" do
expect(Addressable::IDNA.to_unicode("xn--...-")).to eq("xn--...-")
end
it "should convert 'www.google.com' correctly" do
expect(Addressable::IDNA.to_unicode("www.google.com")).to eq(
"www.google.com")
end
it "should convert 'www.詹姆斯.com' correctly" do
expect(Addressable::IDNA.to_unicode(
"www.xn--8ws00zhy3a.com"
)).to eq("www.詹姆斯.com")
end
it "should convert '詹姆斯.com' correctly" do
expect(Addressable::IDNA.to_unicode(
"xn--8ws00zhy3a.com"
)).to eq("詹姆斯.com")
end
it "should convert 'www.iñtërnâtiônàlizætiøn.com' correctly" do
expect(Addressable::IDNA.to_unicode(
"www.xn--itrntinliztin-vdb0a5exd8ewcye.com"
)).to eq("www.iñtërnâtiônàlizætiøn.com")
end
it "should convert 'iñtërnâtiônàlizætiøn.com' correctly" do
expect(Addressable::IDNA.to_unicode(
"xn--itrntinliztin-vdb0a5exd8ewcye.com"
)).to eq("iñtërnâtiônàlizætiøn.com")
end
it "should convert " +
"'www.ほんとうにながいわけのわからないどめいんめいのらべるまだながくしないとたりない.w3.mag.keio.ac.jp' " +
"correctly" do
expect(Addressable::IDNA.to_unicode(
"www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3" +
"fg11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp"
)).to eq(
"www.ほんとうにながいわけのわからないどめいんめいのらべるまだながくしないとたりない.w3.mag.keio.ac.jp"
)
end
it "should convert '点心和烤鸭.w3.mag.keio.ac.jp' correctly" do
expect(Addressable::IDNA.to_unicode(
"xn--0trv4xfvn8el34t.w3.mag.keio.ac.jp"
)).to eq("点心和烤鸭.w3.mag.keio.ac.jp")
end
it "should convert '가각갂갃간갅갆갇갈갉힢힣.com' correctly" do
expect(Addressable::IDNA.to_unicode(
"xn--o39acdefghijk5883jma.com"
)).to eq("가각갂갃간갅갆갇갈갉힢힣.com")
end
it "should convert " +
"'\347\242\274\346\250\231\346\272\226\350" +
"\220\254\345\234\213\347\242\274.com' correctly" do
expect(Addressable::IDNA.to_unicode(
"xn--9cs565brid46mda086o.com"
)).to eq(
"\347\242\274\346\250\231\346\272\226\350" +
"\220\254\345\234\213\347\242\274.com"
)
end
it "should convert 'リ宠퐱卄.com' correctly" do
expect(Addressable::IDNA.to_unicode(
"xn--eek174hoxfpr4k.com"
)).to eq("\343\203\252\345\256\240\355\220\261\345\215\204.com")
end
it "should convert 'ᆵ' correctly" do
expect(Addressable::IDNA.to_unicode(
"xn--4ud"
)).to eq("\341\206\265")
end
it "should convert '🌹🌹🌹.ws' correctly" do
expect(Addressable::IDNA.to_unicode(
"xn--2h8haa.ws"
)).to eq("\360\237\214\271\360\237\214\271\360\237\214\271.ws")
end
it "should handle two adjacent '.'s correctly" do
expect(Addressable::IDNA.to_unicode(
"example..host"
)).to eq("example..host")
end
it "should normalize 'string' correctly" do
expect(Addressable::IDNA.unicode_normalize_kc(:'string')).to eq("string")
expect(Addressable::IDNA.unicode_normalize_kc("string")).to eq("string")
end
end
describe Addressable::IDNA, "when using the pure-Ruby implementation" do
before do
Addressable.send(:remove_const, :IDNA)
load "addressable/idna/pure.rb"
end
it_should_behave_like "converting from unicode to ASCII"
it_should_behave_like "converting from ASCII to unicode"
begin
require "fiber"
it "should not blow up inside fibers" do
f = Fiber.new do
Addressable.send(:remove_const, :IDNA)
load "addressable/idna/pure.rb"
end
f.resume
end
rescue LoadError
# Fibers aren't supported in this version of Ruby, skip this test.
warn('Fibers unsupported.')
end
end
begin
require "idn"
describe Addressable::IDNA, "when using the native-code implementation" do
before do
Addressable.send(:remove_const, :IDNA)
load "addressable/idna/native.rb"
end
it_should_behave_like "converting from unicode to ASCII"
it_should_behave_like "converting from ASCII to unicode"
end
rescue LoadError => error
raise error if ENV["CI"] && TestHelper.native_supported?
# Cannot test the native implementation without libidn support.
warn('Could not load native IDN implementation.')
end

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
# coding: utf-8
# Copyright (C) Bob Aman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require "spec_helper"
require "addressable/uri"
require "net/http"
describe Net::HTTP do
it "should be compatible with Addressable" do
response_body =
Net::HTTP.get(Addressable::URI.parse('http://www.google.com/'))
expect(response_body).not_to be_nil
end
end

View File

@ -0,0 +1,59 @@
# frozen_string_literal: true
# coding: utf-8
# Copyright (C) Bob Aman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require "spec_helper"
require "addressable/uri"
describe Addressable::URI, "when created with a URI known to cause crashes " +
"in certain browsers" do
it "should parse correctly" do
uri = Addressable::URI.parse('%%30%30')
expect(uri.path).to eq('%%30%30')
expect(uri.normalize.path).to eq('%2500')
end
it "should parse correctly as a full URI" do
uri = Addressable::URI.parse('http://www.example.com/%%30%30')
expect(uri.path).to eq('/%%30%30')
expect(uri.normalize.path).to eq('/%2500')
end
end
describe Addressable::URI, "when created with a URI known to cause crashes " +
"in certain browsers" do
it "should parse correctly" do
uri = Addressable::URI.parse('لُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ 冗')
expect(uri.path).to eq('لُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ 冗')
expect(uri.normalize.path).to eq(
'%D9%84%D9%8F%D8%B5%D9%91%D8%A8%D9%8F%D9%84%D9%8F%D9%84%D8%B5%D9%91' +
'%D8%A8%D9%8F%D8%B1%D8%B1%D9%8B%20%E0%A5%A3%20%E0%A5%A3h%20%E0%A5' +
'%A3%20%E0%A5%A3%20%E5%86%97'
)
end
it "should parse correctly as a full URI" do
uri = Addressable::URI.parse('http://www.example.com/لُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ 冗')
expect(uri.path).to eq('/لُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ 冗')
expect(uri.normalize.path).to eq(
'/%D9%84%D9%8F%D8%B5%D9%91%D8%A8%D9%8F%D9%84%D9%8F%D9%84%D8%B5%D9%91' +
'%D8%A8%D9%8F%D8%B1%D8%B1%D9%8B%20%E0%A5%A3%20%E0%A5%A3h%20%E0%A5' +
'%A3%20%E0%A5%A3%20%E5%86%97'
)
end
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'bundler/setup'
require 'rspec/its'
begin
require 'coveralls'
Coveralls.wear! do
add_filter "spec/"
add_filter "vendor/"
end
rescue LoadError
warn "warning: coveralls gem not found; skipping Coveralls"
require 'simplecov'
SimpleCov.start do
add_filter "spec/"
add_filter "vendor/"
end
end if Gem.loaded_specs.key?("simplecov")
class TestHelper
def self.native_supported?
mri = RUBY_ENGINE == "ruby"
windows = RUBY_PLATFORM.include?("mingw")
mri && !windows
end
end
RSpec.configure do |config|
config.warnings = true
config.filter_run_when_matching :focus
end

View File

@ -0,0 +1,4 @@
# frozen_string_literal: true
desc "Remove all build products"
task "clobber"

View File

@ -0,0 +1,92 @@
# frozen_string_literal: true
require "rubygems/package_task"
namespace :gem do
GEM_SPEC = Gem::Specification.new do |s|
s.name = PKG_NAME
s.version = PKG_VERSION
s.summary = PKG_SUMMARY
s.description = PKG_DESCRIPTION
s.files = PKG_FILES.to_a
s.extra_rdoc_files = %w( README.md )
s.rdoc_options.concat ["--main", "README.md"]
if !s.respond_to?(:add_development_dependency)
puts "Cannot build Gem with this version of RubyGems."
exit(1)
end
s.required_ruby_version = ">= 2.0"
s.add_runtime_dependency "public_suffix", ">= 2.0.2", "< 5.0"
s.add_development_dependency "bundler", ">= 1.0", "< 3.0"
s.require_path = "lib"
s.author = "Bob Aman"
s.email = "bob@sporkmonger.com"
s.homepage = "https://github.com/sporkmonger/addressable"
s.license = "Apache-2.0"
end
Gem::PackageTask.new(GEM_SPEC) do |p|
p.gem_spec = GEM_SPEC
p.need_tar = true
p.need_zip = true
end
desc "Generates .gemspec file"
task :gemspec do
spec_string = GEM_SPEC.to_ruby
File.open("#{GEM_SPEC.name}.gemspec", "w") do |file|
file.write spec_string
end
end
desc "Show information about the gem"
task :debug do
puts GEM_SPEC.to_ruby
end
desc "Install the gem"
task :install => ["clobber", "gem:package"] do
sh "#{SUDO} gem install --local pkg/#{GEM_SPEC.full_name}"
end
desc "Uninstall the gem"
task :uninstall do
installed_list = Gem.source_index.find_name(PKG_NAME)
if installed_list &&
(installed_list.collect { |s| s.version.to_s}.include?(PKG_VERSION))
sh(
"#{SUDO} gem uninstall --version '#{PKG_VERSION}' " +
"--ignore-dependencies --executables #{PKG_NAME}"
)
end
end
desc "Reinstall the gem"
task :reinstall => [:uninstall, :install]
desc "Package for release"
task :release => ["gem:package", "gem:gemspec"] do |t|
v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
abort "Versions don't match #{v} vs #{PROJ.version}" if v != PKG_VERSION
pkg = "pkg/#{GEM_SPEC.full_name}"
changelog = File.open("CHANGELOG.md") { |file| file.read }
puts "Releasing #{PKG_NAME} v. #{PKG_VERSION}"
Rake::Task["git:tag:create"].invoke
end
end
desc "Alias to gem:package"
task "gem" => "gem:package"
task "gem:release" => "gem:gemspec"
task "clobber" => ["gem:clobber_package"]

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
namespace :git do
namespace :tag do
desc "List tags from the Git repository"
task :list do
tags = `git tag -l`
tags.gsub!("\r", "")
tags = tags.split("\n").sort {|a, b| b <=> a }
puts tags.join("\n")
end
desc "Create a new tag in the Git repository"
task :create do
changelog = File.open("CHANGELOG.md", "r") { |file| file.read }
puts "-" * 80
puts changelog
puts "-" * 80
puts
v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
abort "Versions don't match #{v} vs #{PKG_VERSION}" if v != PKG_VERSION
git_status = `git status`
if git_status !~ /^nothing to commit/
abort "Working directory isn't clean."
end
tag = "#{PKG_NAME}-#{PKG_VERSION}"
msg = "Release #{PKG_NAME}-#{PKG_VERSION}"
existing_tags = `git tag -l #{PKG_NAME}-*`.split('\n')
if existing_tags.include?(tag)
warn("Tag already exists, deleting...")
unless system "git tag -d #{tag}"
abort "Tag deletion failed."
end
end
puts "Creating git tag '#{tag}'..."
unless system "git tag -a -m \"#{msg}\" #{tag}"
abort "Tag creation failed."
end
end
end
end
task "gem:release" => "git:tag:create"

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
namespace :metrics do
task :lines do
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
for file_name in FileList["lib/**/*.rb"]
f = File.open(file_name)
while line = f.gets
lines += 1
next if line =~ /^\s*$/
next if line =~ /^\s*#/
codelines += 1
end
puts "L: #{sprintf("%4d", lines)}, " +
"LOC #{sprintf("%4d", codelines)} | #{file_name}"
total_lines += lines
total_codelines += codelines
lines, codelines = 0, 0
end
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
end
end

View File

@ -0,0 +1,72 @@
# frozen_string_literal: true
namespace :profile do
desc "Profile Template match memory allocations"
task :template_match_memory do
require "memory_profiler"
require "addressable/template"
start_at = Time.now.to_f
template = Addressable::Template.new("http://example.com/{?one,two,three}")
report = MemoryProfiler.report do
30_000.times do
template.match(
"http://example.com/?one=one&two=floo&three=me"
)
end
end
end_at = Time.now.to_f
print_options = { scale_bytes: true, normalize_paths: true }
puts "\n\n"
if ENV["CI"]
report.pretty_print(print_options)
else
t_allocated = report.scale_bytes(report.total_allocated_memsize)
t_retained = report.scale_bytes(report.total_retained_memsize)
puts "Total allocated: #{t_allocated} (#{report.total_allocated} objects)"
puts "Total retained: #{t_retained} (#{report.total_retained} objects)"
puts "Took #{end_at - start_at} seconds"
FileUtils.mkdir_p("tmp")
report.pretty_print(to_file: "tmp/memprof.txt", **print_options)
end
end
desc "Profile URI parse memory allocations"
task :memory do
require "memory_profiler"
require "addressable/uri"
if ENV["IDNA_MODE"] == "pure"
Addressable.send(:remove_const, :IDNA)
load "addressable/idna/pure.rb"
end
start_at = Time.now.to_f
report = MemoryProfiler.report do
30_000.times do
Addressable::URI.parse(
"http://google.com/stuff/../?with_lots=of&params=asdff#!stuff"
).normalize
end
end
end_at = Time.now.to_f
print_options = { scale_bytes: true, normalize_paths: true }
puts "\n\n"
if ENV["CI"]
report.pretty_print(**print_options)
else
t_allocated = report.scale_bytes(report.total_allocated_memsize)
t_retained = report.scale_bytes(report.total_retained_memsize)
puts "Total allocated: #{t_allocated} (#{report.total_allocated} objects)"
puts "Total retained: #{t_retained} (#{report.total_retained} objects)"
puts "Took #{end_at - start_at} seconds"
FileUtils.mkdir_p("tmp")
report.pretty_print(to_file: "tmp/memprof.txt", **print_options)
end
end
end

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
require "rspec/core/rake_task"
namespace :spec do
RSpec::Core::RakeTask.new(:simplecov) do |t|
t.pattern = FileList['spec/**/*_spec.rb']
t.rspec_opts = %w[--color --format documentation] unless ENV["CI"]
end
namespace :simplecov do
desc "Browse the code coverage report."
task :browse => "spec:simplecov" do
require "launchy"
Launchy.open("coverage/index.html")
end
end
end
desc "Alias to spec:simplecov"
task "spec" => "spec:simplecov"
task "clobber" => ["spec:clobber_simplecov"]

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
require "rake"
begin
require "yard"
require "yard/rake/yardoc_task"
namespace :doc do
desc "Generate Yardoc documentation"
YARD::Rake::YardocTask.new do |yardoc|
yardoc.name = "yard"
yardoc.options = ["--verbose", "--markup", "markdown"]
yardoc.files = FileList[
"lib/**/*.rb", "ext/**/*.c",
"README.md", "CHANGELOG.md", "LICENSE.txt"
].exclude(/idna/)
end
end
task "clobber" => ["doc:clobber_yard"]
desc "Alias to doc:yard"
task "doc" => "doc:yard"
rescue LoadError
# If yard isn't available, it's not the end of the world
desc "Alias to doc:rdoc"
task "doc" => "doc:rdoc"
end

View File

@ -0,0 +1,9 @@
Provides functions for lunar phases and lunar month dates (eg. new/full moon)
This file is a thoughtless manual compilation of Astro::MoonPhase perl
module (which, in turn, is influenced by moontool.c). I don't know
how it all works.
This gem contains an example script (bin/moon.rb), which hopefully helps to
understand the API of this module.

View File

@ -0,0 +1,337 @@
require 'date'
module Astro # :nodoc:
#
# Provides functions for lunar phases and lunar month dates (eg. new/full
# moon)
#
# This file is a thoughtless manual compilation of Astro::MoonPhase perl
# module (which, in turn, is influenced by moontool.c). I don't know
# how it all works.
module Moon # :doc:
class << self
# a container structure for phase() return value. accessors:
# phase - moon phase, 0 or 1 for new moon, 0.5 for full moon, etc
# illumination - moon illumination, 0.0 .. 0.1
# age - moon age, in days from the most recent new moon
# distance - moon distance from earth, in kilometers
# angle - moon angle
# sun_distance - sun distance from earth, in kilometers
# sun_angle - sun angle
Phase = Struct.new(:phase, :illumination, :age, :distance, :angle,
:sun_distance, :sun_angle)
# a container structure for phasehunt() return value. accessors:
# moon_start - a DateTime of the most recent new moon
# moon_end - a DateTime of the next new moon
# moon_fill - a DateTime of the full moon of this lunar month
# first_quarter, last_quarter -- <...>
PhaseHunt = Struct.new(:moon_start, :first_quarter, :moon_full,
:last_quarter, :moon_end)
# Astronomical constants.
# 1980 January 0.0
EPOCH = 2444238.5
# Constants defining the Sun's apparent orbit.
#
# ecliptic longitude of the Sun at epoch 1980.0
ELONGE = 278.833540
# ecliptic longitude of the Sun at perigee
ELONGP = 282.596403
# eccentricity of Earth's orbit
ECCENT = 0.016718
# semi-major axis of Earth's orbit, km
SUNSMAX = 1.495985e8
# sun's angular size, degrees, at semi-major axis distance
SUNANGSIZ = 0.533128
# Elements of the Moon's orbit, epoch 1980.0.
# moon's mean longitude at the epoch
MMLONG = 64.975464
# mean longitude of the perigee at the epoch
MMLONGP = 349.383063
# mean longitude of the node at the epoch
MLNODE = 151.950429
# inclination of the Moon's orbit
MINC = 5.145396
# eccentricity of the Moon's orbit
MECC = 0.054900
# moon's angular size at distance a from Earth
MANGSIZ = 0.5181
# semi-major axis of Moon's orbit in km
MSMAX = 384401.0
# parallax at distance a from Earth
MPARALLAX = 0.9507
# synodic month (new Moon to new Moon)
SYNMONTH = 29.53058868
# Finds the key dates of the specified lunar month.
# Takes a DateTime object (or creates one using now() function).
# Returns a PhaseHunt struct instance. (see Constants section)
#
# # find the date/time of the full moon in this lunar month
# Astro::Moon.phasehunt.moon_full.strftime("%D %T %Z") \
# # => "03/04/07 02:17:40 +0300"
def phasehunt(date = nil)
date = DateTime.now if !date
sdate = date.ajd
adate = sdate - 45
ad1 = DateTime.jd(adate)
k1 = ((ad1.year + ((ad1.month - 1) *
(1.0 / 12.0)) - 1900) * 12.3685).floor
adate = nt1 = meanphase(adate, k1)
while true
adate += SYNMONTH
k2 = k1 + 1
nt2 = meanphase(adate, k2)
break if nt1 <= sdate && nt2 > sdate
nt1 = nt2
k1 = k2
end
return PhaseHunt.new(*[
truephase(k1, 0.0),
truephase(k1, 0.25),
truephase(k1, 0.5),
truephase(k1, 0.75),
truephase(k2, 0.0)
].map {
|_| _.new_offset(date.offset)
})
end
# Finds the lunar phase for the specified date.
# Takes a DateTime object (or creates one using now() function).
# Returns a Phase struct instance. (see Constants section)
#
# # find the current moon illumination, in percents
# Astro::Moon.phase.illumination * 100 # => 63.1104513958699
#
# # find the current moon phase (new moon is 0 or 100,
# # full moon is 50, etc)
# Astro::Moon.phase.phase * 100 # => 70.7802812241989
def phase(dt = nil)
dt = DateTime.now if !dt
pdate = dt.ajd
# Calculation of the Sun's position.
day = pdate - EPOCH # date within epoch
n = ((360 / 365.2422) * day) % 360.0
m = (n + ELONGE - ELONGP) % 360.0 # convert from perigee
# co-ordinates to epoch 1980.0
ec = kepler(m, ECCENT) # solve equation of Kepler
ec = Math.sqrt((1 + ECCENT) / (1 - ECCENT)) * Math.tan(ec / 2)
ec = 2 * todeg(Math.atan(ec)) # true anomaly
lambdasun = (ec + ELONGP) % 360.0 # Sun's geocentric ecliptic
# longitude
# Orbital distance factor.
f = ((1 + ECCENT * Math.cos(torad(ec))) / (1 - ECCENT * ECCENT))
sundist = SUNSMAX / f # distance to Sun in km
sunang = f * SUNANGSIZ # Sun's angular size in degrees
# Calculation of the Moon's position.
# Moon's mean longitude.
ml = (13.1763966 * day + MMLONG) % 360.0
# Moon's mean anomaly.
mm = (ml - 0.1114041 * day - MMLONGP) % 360.0
# Moon's ascending node mean longitude.
mn = (MLNODE - 0.0529539 * day) % 360.0
# Evection.
ev = 1.2739 * Math.sin(torad(2 * (ml - lambdasun) - mm))
# Annual equation.
ae = 0.1858 * Math.sin(torad(m))
# Correction term.
a3 = 0.37 * Math.sin(torad(m))
# Corrected anomaly.
mmp = mm + ev - ae - a3
# Correction for the equation of the centre.
mec = 6.2886 * Math.sin(torad(mmp))
# Another correction term.
a4 = 0.214 * Math.sin(torad(2 * mmp))
# Corrected longitude.
lp = ml + ev + mec - ae + a4
# Variation.
v = 0.6583 * Math.sin(torad(2 * (lp - lambdasun)))
# True longitude.
lpp = lp + v
# Corrected longitude of the node.
np = mn - 0.16 * Math.sin(torad(m))
# Y inclination coordinate.
y = Math.sin(torad(lpp - np)) * Math.cos(torad(MINC))
# X inclination coordinate.
x = Math.cos(torad(lpp - np))
# Ecliptic longitude.
lambdamoon = todeg(Math.atan2(y, x))
lambdamoon += np
# Ecliptic latitude.
betam = todeg(Math.asin(Math.sin(torad(lpp - np)) *
Math.sin(torad(MINC))))
# Calculation of the phase of the Moon.
# Age of the Moon in degrees.
moonage = lpp - lambdasun
# Phase of the Moon.
moonphase = (1 - Math.cos(torad(moonage))) / 2
# Calculate distance of moon from the centre of the Earth.
moondist = (MSMAX * (1 - MECC * MECC)) /
(1 + MECC * Math.cos(torad(mmp + mec)))
# Calculate Moon's angular diameter.
moondfrac = moondist / MSMAX
moonang = MANGSIZ / moondfrac
# Calculate Moon's parallax.
moonpar = MPARALLAX / moondfrac
pphase = moonphase
mpfrac = (moonage % 360) / 360.0
mage = SYNMONTH * mpfrac
dist = moondist
angdia = moonang
sudist = sundist
suangdia = sunang
Phase.new(mpfrac, pphase, mage, dist, angdia, sudist, suangdia)
end
private
def torad(x) ; x * Math::PI / 180.0 ; end
def todeg(x) ; x * 180.0 / Math::PI ; end
def dsin(x) ; Math.sin(torad(x)) ; end
def dcos(x) ; Math.sin(torad(x)) ; end
def meanphase(sdate, k)
## Time in Julian centuries from 1900 January 0.5
t = (sdate - 2415020.0) / 36525
t2 = t * t
t3 = t2 * t
nt1 = 2415020.75933 +
SYNMONTH * k +
0.0001178 * t2 -
0.000000155 * t3 +
0.00033 * dsin(166.56 + 132.87 * t - 0.009173 * t2)
end
def truephase(k, phase)
apcor = 0
k += phase # add phase to new moon time
t = k / 1236.85 # time in Julian centuries from
# 1900 January 0.5
t2 = t * t # square for frequent use
t3 = t2 * t # cube for frequent use
# mean time of phase */
pt = 2415020.75933 +
SYNMONTH * k +
0.0001178 * t2 -
0.000000155 * t3 +
0.00033 * dsin(166.56 + 132.87 * t - 0.009173 * t2)
# Sun's mean anomaly
m = 359.2242 + 29.10535608 * k - 0.0000333 * t2 - 0.00000347 * t3
# Moon's mean anomaly
mprime =
306.0253 + 385.81691806 * k + 0.0107306 * t2 + 0.00001236 * t3
# Moon's argument of latitude
f = 21.2964 + 390.67050646 * k - 0.0016528 * t2 - 0.00000239 * t3
if phase < 0.01 || (phase - 0.5).abs < 0.01
# Corrections for New and Full Moon.
pt += (0.1734 - 0.000393 * t) * dsin(m) +
0.0021 * dsin(2 * m) -
0.4068 * dsin(mprime) +
0.0161 * dsin(2 * mprime) -
0.0004 * dsin(3 * mprime) +
0.0104 * dsin(2 * f) -
0.0051 * dsin(m + mprime) -
0.0074 * dsin(m - mprime) +
0.0004 * dsin(2 * f + m) -
0.0004 * dsin(2 * f - m) -
0.0006 * dsin(2 * f + mprime) +
0.0010 * dsin(2 * f - mprime) +
0.0005 * dsin(m + 2 * mprime)
apcor = 1
elsif (phase - 0.25).abs < 0.01 || (phase - 0.75).abs < 0.01
pt += (0.1721 - 0.0004 * t) * dsin(m) +
0.0021 * dsin(2 * m) -
0.6280 * dsin(mprime) +
0.0089 * dsin(2 * mprime) -
0.0004 * dsin(3 * mprime) +
0.0079 * dsin(2 * f) -
0.0119 * dsin(m + mprime) -
0.0047 * dsin(m - mprime) +
0.0003 * dsin(2 * f + m) -
0.0004 * dsin(2 * f - m) -
0.0006 * dsin(2 * f + mprime) +
0.0021 * dsin(2 * f - mprime) +
0.0003 * dsin(m + 2 * mprime) +
0.0004 * dsin(m - 2 * mprime) -
0.0003 * dsin(2 * m + mprime)
corr = 0.0028 - 0.0004 * dcos(m) + 0.0003 * dcos(mprime)
pt += (phase < 0.5 ? corr : -corr)
apcor = 1;
end
if !apcor || apcor == 0
raise "truephase() called with invalid phase selector (#{phase})."
end
return DateTime.jd(pt + 0.5)
end
def kepler(m, ecc)
m = torad(m)
e = m;
loop do
delta = e - ecc * Math.sin(e) - m
e -= delta / (1 - ecc * Math.cos(e))
break if delta.abs <= 1e-6
end
return e
end
end
end
end

View File

@ -0,0 +1,126 @@
version: 2.1
commands:
setup-env:
description: Sets up the testing environment
steps:
- run:
name: Install OS packages
command: apk add git build-base ruby-dev ruby-etc ruby-json libsodium
- checkout
- run:
name: "Ruby version"
command: |
ruby -v
echo $RUBY_VERSION > ruby_version.txt
- restore_cache:
keys:
- bundle-cache-v1-{{ checksum "ruby_version.txt" }}-{{ .Branch }}-{{ checksum "Gemfile" }}-{{ checksum "discordrb.gemspec" }}
- bundle-cache-v1-{{ checksum "ruby_version.txt" }}-{{ .Branch }}
- bundle-cache-v1-{{ checksum "ruby_version.txt" }}
- run:
name: Install dependencies
command: bundle install --path vendor/bundle
- save_cache:
key: bundle-cache-v1-{{ checksum "ruby_version.txt" }}-{{ .Branch }}-{{ checksum "Gemfile" }}-{{ checksum "discordrb.gemspec" }}
paths:
- ./vendor/bundle
jobs:
test_ruby_25:
docker:
- image: ruby:2.5-alpine
steps:
- setup-env
- run:
name: Run RSpec
command: bundle exec rspec
test_ruby_26:
docker:
- image: ruby:2.6-alpine
steps:
- setup-env
- run:
name: Run RSpec
command: bundle exec rspec
test_ruby_27:
docker:
- image: ruby:2.7-alpine
steps:
- setup-env
- run:
name: Run RSpec
command: bundle exec rspec
rubocop:
docker:
- image: ruby:2.5-alpine
steps:
- setup-env
- run:
name: Run Rubocop
command: bundle exec rubocop -P
yard:
docker:
- image: ruby:2.5-alpine
steps:
- setup-env
- attach_workspace:
at: /tmp/workspace
- run:
name: Run YARD
command: bundle exec yard --output-dir /tmp/workspace/docs
- persist_to_workspace:
root: /tmp/workspace
paths:
- docs
pages:
machine: true
steps:
- attach_workspace:
at: /tmp/workspace
- run:
name: Clone docs
command: git clone $CIRCLE_REPOSITORY_URL -b gh-pages .
- add_ssh_keys:
fingerprints:
- "9a:4c:50:94:23:46:81:74:41:97:87:04:4e:59:4b:4e"
- run:
name: Push updated docs
command: |
git config user.name "Circle CI"
git config user.email "ci-build@shardlab.dev"
SOURCE_BRANCH=$CIRCLE_BRANCH
if [ -n "$CIRCLE_TAG" ]; then
SOURCE_BRANCH=$CIRCLE_TAG
fi
mkdir -p $SOURCE_BRANCH
rm -rf $SOURCE_BRANCH/*
cp -r /tmp/workspace/docs/. ./$SOURCE_BRANCH/
git add $SOURCE_BRANCH
git commit --allow-empty -m "[skip ci] Deploy docs"
git push -u origin gh-pages
workflows:
test:
jobs:
- test_ruby_25
- test_ruby_26
- test_ruby_27
- rubocop
- yard
deploy:
jobs:
- yard:
filters: {branches: {only: main}, tags: {only: /^v.*/}}
- pages:
requires:
- yard
filters: {branches: {only: main}, tags: {only: /^v.*/}}

View File

@ -0,0 +1,16 @@
---
engines:
duplication:
enabled: true
config:
languages:
- ruby
fixme:
enabled: true
rubocop:
enabled: true
ratings:
paths:
- "**.rb"
exclude_paths:
- spec/

View File

@ -0,0 +1,13 @@
# Contributing guidelines
Any contributions are very much appreciated! This project is relatively relaxed when it comes to guidelines, however
there are still some things that would be nice to have considered.
For bug reports, please try to include a code sample if at all appropriate for
the issue, so we can reproduce it on our own machines.
For PRs, please make sure that you tested your code before every push; it's a little annoying to keep having to get back
to a PR because of small avoidable oversights. (Huge bonus points if you're adding specs for your code! This project
has very few specs in places where it should have more so every added spec is very much appreciated.)
If you have any questions at all, don't be afraid to ask in the [discordrb channel on Discord](https://discord.gg/cyK3Hjm).

View File

@ -0,0 +1,39 @@
---
name: Bug report
about: Report a bug to help us improve the library
---
# Summary
<!---
First, please check to see that another issue or pull request (open or closed)
already addresses the problem you are facing. If you are not sure, please ask
in the Discord channel (link below).
Describe the bug you are encountering with as much detail as you can.
If you are not sure if a small detail is relevant, include it anyways!
Include simple code examples that will reproduce the bug.
Include any exceptions you are encountering in your logs, including
the initial error message and the backtrace that follows.
Stuck or need help? Join the Discord! https://discord.gg/cyK3Hjm
-->
---
## Environment
<!---
These are some commands to run to give us basic information about
your Ruby environment. Some issues may be version, OS, or hardware specific.
--->
**Ruby version:**
<!--- Paste full output of `ruby -v` here --->
**Discordrb version:**
<!--- Paste full output of `gem list discordrb` or `bundle list discordrb` here --->

View File

@ -0,0 +1,25 @@
---
name: Feature Request
about: Request a new feature, or change an existing one
---
<!---
Describe your feature request in as much detail as you can here.
Include why you want this feature. For example:
- It is a feature supported by Discord's API, but missing from the library
- It is a feature that would simplify a lot of your bots code
- It is a feature present in other popular Discord libraries
Include simple code examples (pseudocode is fine) that demonstrate
what you want to do.
Do your best to think about how this new feature would impact the code
of other people who use discordrb, and not just your own bot's codebase.
Stuck or need help? Join the Discord! https://discord.gg/cyK3Hjm
--->

View File

@ -0,0 +1,37 @@
# Summary
<!---
Describe the general goal of your pull request here:
- Include details about any issues you encountered that inspired
the pull request, such as bugs or missing features.
- If adding or changing features, include a small example that showcases
the new behavior.
- Reference any related issues or pull requests.
Stuck or need help? Join the Discord! https://discord.gg/cyK3Hjm
--->
---
<!---
List each individual change you made to the library here in the appropriate
section in short, bulleted sentences.
This will aid in reviewing your pull request, and for adding it to the
changelog later.
You may remove sections that have no items if you want.
--->
## Added
## Changed
## Deprecated
## Removed
## Fixed

View File

@ -0,0 +1,16 @@
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/profiles/
/tmp/
/.idea/
.DS_Store
*.gem
**.sw*
/vendor/bundle

View File

@ -0,0 +1,7 @@
PreCommit:
RuboCop:
enabled: true
on_warn: fail # Treat all warnings as failures
AuthorName:
enabled: false

View File

@ -0,0 +1,2 @@
--color
--require spec_helper

View File

@ -0,0 +1,62 @@
require: rubocop-performance
inherit_mode:
merge:
- AllowedNames
AllCops:
NewCops: enable
TargetRubyVersion: 2.5
# Disable line length checks
Layout/LineLength:
Enabled: false
# TODO: Larger refactor
Lint/MissingSuper:
Enabled: false
# Allow 'Pokémon-style' exception handling
Lint/RescueException:
Enabled: false
# Disable all metrics.
Metrics:
Enabled: false
# Allow some common and/or obvious short method params
Naming/MethodParameterName:
AllowedNames:
- e
# Ignore `eval` in the examples folder
Security/Eval:
Exclude:
- examples/**/*
# https://stackoverflow.com/q/4763121/
Style/Alias:
Enabled: false
# Prefer compact module/class defs
Style/ClassAndModuleChildren:
Enabled: false
# So RuboCop doesn't complain about application IDs
Style/NumericLiterals:
Exclude:
- examples/**/*
# TODO: Requires breaking changes
Style/OptionalBooleanParameter:
Enabled: false
# Prefer explicit arguments in case global variables like `$;` or `$,` are changed
Style/RedundantArgument:
Enabled: false
# Prefer |m, e| for the `reduce` block arguments
Style/SingleLineBlockParams:
Methods:
- reduce: [m, e]
- inject: [m, e]

View File

@ -0,0 +1,32 @@
language: ruby
rvm:
- 2.5
- 2.6
before_script:
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 -o ./cc-test-reporter
- chmod +x ./cc-test-reporter
- ./cc-test-reporter before-build
- sudo apt-get install libsodium-dev
script:
- bundle exec rspec spec
- bundle exec rubocop -c .rubocop.yml
- bin/travis_build_docs.sh
after_script:
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
deploy:
- provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
keep-history: true
local-dir: docs
on:
branch: master
rvm: 2.6
- provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
keep-history: true
local-dir: docs
on:
tags: true
rvm: 2.6

View File

@ -0,0 +1 @@
--markup markdown --hide-tag todo --fail-on-warning

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
source 'https://rubygems.org'
# Specify your gem's dependencies in discordrb.gemspec
gemspec name: 'discordrb'
gemspec name: 'discordrb-webhooks', development_group: 'webhooks'

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2020 meew0
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,174 @@
[![Gem](https://img.shields.io/gem/v/discordrb.svg)](https://rubygems.org/gems/discordrb)
[![Gem](https://img.shields.io/gem/dt/discordrb.svg)](https://rubygems.org/gems/discordrb)
[![CircleCI](https://circleci.com/gh/shardlab/discordrb.svg?style=svg)](https://circleci.com/gh/shardlab/discordrb)
[![Inline docs](http://inch-ci.org/github/shardlab/discordrb.svg?branch=main)](http://inch-ci.org/github/shardlab/discordrb)
[![Join Discord](https://img.shields.io/badge/discord-join-7289DA.svg)](https://discord.gg/cyK3Hjm)
# discordrb
An implementation of the [Discord](https://discord.com/) API using Ruby.
## Quick links to sections
* [Introduction](https://github.com/shardlab/discordrb#introduction)
* [Dependencies](https://github.com/shardlab/discordrb#dependencies)
* [Installation](https://github.com/shardlab/discordrb#installation)
* [Usage](https://github.com/shardlab/discordrb#usage)
* [Webhooks Client](https://github.com/shardlab/discordrb#webhooks-client)
* [Support](https://github.com/shardlab/discordrb#support)
* [Development](https://github.com/shardlab/discordrb#development), [Contributing](https://github.com/shardlab/discordrb#contributing)
* [License](https://github.com/shardlab/discordrb#license)
See also: [Documentation](https://www.rubydoc.info/gems/discordrb), [Tutorials](https://github.com/shardlab/discordrb/wiki)
## Introduction
`discordrb` aims to meet the following design goals:
1. Full coverage of the public bot API.
2. Expressive, high level abstractions for rapid development of common applications.
3. Friendly to Ruby beginners and beginners of open source contribution.
If you enjoy using the library, consider getting involved with the community to help us improve and meet these goals!
**You should consider using `discordrb` if:**
- You need a bot - and fast - for small or medium sized communities, and don't want to be bogged down with "low level" details. Getting started takes minutes, and utilities like a command parser and tools for modularization make it simple to quickly add or change your bots functionality.
- You like or want to learn Ruby, or want to contribute to a Ruby project. A lot of our users are new to Ruby, and eventually make their first open source contributions with us. We have an active Discord channel with experienced members who will happily help you get involved, either as a user or contributor.
- You want to experiment with Discord's API or prototype concepts for Discord bots without too much commitment.
**You should consider other libraries if:**
- You need to scale to large volumes of servers (>2,500) with lots of members. It's still possible, but it can be difficult to scale Ruby processes, and it requires more in depth knowledge to do so well. Especially if you already have a bot that is on a large amount of servers, porting to Ruby is unlikely to improve your performance in most cases.
- You want full control over the library that you're using. While we expose some "lower level" interfaces, they are unstable, and only exist to serve the more powerful abstractions in the library.
## Dependencies
* Ruby >= 2.5 supported
* An installed build system for native extensions (on Windows, make sure you download the "Ruby+Devkit" version of [RubyInstaller](https://rubyinstaller.org/downloads/))
### Voice dependencies
This section only applies to you if you want to use voice functionality.
* [libsodium](https://github.com/shardlab/discordrb/wiki/Installing-libsodium)
* A compiled libopus distribution for your system, anywhere the script can find it. See [here](https://github.com/shardlab/discordrb/wiki/Installing-libopus) for installation instructions.
* [FFmpeg](https://www.ffmpeg.org/download.html) installed and in your PATH
## Installation
### With Bundler
Using [Bundler](https://bundler.io/#getting-started), you can add discordrb to your Gemfile:
gem 'discordrb'
And then install via `bundle install`.
Run the [ping example](https://github.com/shardlab/discordrb/blob/master/examples/ping.rb) to verify that the installation works (make sure to replace the token and client ID in there with your bots'!):
To run the bot while using bundler:
bundle exec ruby ping.rb
### With Gem
Alternatively, while Bundler is the recommended option, you can also install discordrb without it.
#### Linux / macOS
gem install discordrb
#### Windows
> **Make sure you have the DevKit installed! See the [Dependencies](https://github.com/shardlab/discordrb#dependencies) section)**
gem install discordrb --platform=ruby
To run the bot:
ruby ping.rb
### Installation Troubleshooting
See https://github.com/shardlab/discordrb/wiki/FAQ#installation for a list of common problems and solutions when installing `discordrb`.
## Usage
You can make a simple bot like this:
```ruby
require 'discordrb'
bot = Discordrb::Bot.new token: '<token here>'
bot.message(with_text: 'Ping!') do |event|
event.respond 'Pong!'
end
bot.run
```
This bot responds to every "Ping!" with a "Pong!".
See [additional examples here](https://github.com/shardlab/discordrb/tree/master/examples).
You can find examples of projects that use discordrb by [searching for the discordrb topic on GitHub](https://github.com/topics/discordrb).
If you've made an open source project on GitHub that uses discordrb, consider adding the `discordrb` topic to your repo!
## Webhooks Client
Also included is a webhooks client, which can be used as a separate gem `discordrb-webhooks`. This special client can be used to form requests to Discord webhook URLs in a high-level manner.
- [`discordrb-webhooks` documentation](https://www.rubydoc.info/gems/discordrb-webhooks)
- [More information about webhooks](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks)
- [Embed visualizer tool](https://leovoel.github.io/embed-visualizer/) - Includes a discordrb code generator for forming embeds
### Usage
```ruby
require 'discordrb/webhooks'
WEBHOOK_URL = 'https://discord.com/api/webhooks/424070213278105610/yByxDncRvHi02mhKQheviQI2erKkfRRwFcEp0MMBfib1ds6ZHN13xhPZNS2-fJo_ApSw'.freeze
client = Discordrb::Webhooks::Client.new(url: WEBHOOK_URL)
client.execute do |builder|
builder.content = 'Hello world!'
builder.add_embed do |embed|
embed.title = 'Embed title'
embed.description = 'Embed description'
embed.timestamp = Time.now
end
end
```
**Note:** The `discordrb` gem relies on `discordrb-webhooks`. If you already have `discordrb` installed, `require 'discordrb/webhooks'` will include all of the `Webhooks` features as well.
## Support
If you need help or have a question, you can:
1. Join our [Discord channel](https://discord.gg/cyK3Hjm). This is the fastest means of getting support.
2. [Open an issue](https://github.com/shardlab/discordrb/issues). Be sure to read the issue template, and provide as much detail as you can.
## Contributing
Thank you for your interest in contributing!
Bug reports and pull requests are welcome on GitHub at https://github.com/shardlab/discordrb.
In general, we recommend starting by discussing what you would like to contribute in the [Discord channel](https://discord.gg/cyK3Hjm).
There are usually a handful of people working on things for the library, and what you're looking for may already be on the way.
Additionally, there is a chance what you are looking for might already exist, or we decided not to pursue it for some reason.
Be sure to use the search feature on our documentation, GitHub, and Discord to see if this might be the case.
## Development setup
**This section is for developing discordrb itself! If you just want to make a bot, see the [Installation](https://github.com/shardlab/discordrb#installation) section.**
After checking out the repo, run `bin/setup` to install dependencies. You can then run tests via `bundle exec rspec spec`. Make sure to run rubocop also: `bundle exec rubocop`. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
require 'bundler/gem_helper'
namespace :main do
Bundler::GemHelper.install_tasks(name: 'discordrb')
end
namespace :webhooks do
Bundler::GemHelper.install_tasks(name: 'discordrb-webhooks')
end
task build: %i[main:build webhooks:build]
task release: %i[main:release webhooks:release]
# Make "build" the default task
task default: :build

View File

@ -0,0 +1,15 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'bundler/setup'
require 'discordrb'
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require 'irb'
IRB.start

View File

@ -0,0 +1,7 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
bundle install
# Do any other automated setup that you need to do here

View File

@ -0,0 +1,17 @@
#!/bin/bash
# Script to build docs for travis usage
set -e
SOURCE_BRANCH=$TRAVIS_BRANCH
if [ -n "$TRAVIS_TAG" ]; then
SOURCE_BRANCH=$TRAVIS_TAG
fi
# Use YARD to build docs
bundle exec yard
# Move to correct folder
mkdir -p docs/$SOURCE_BRANCH
mv doc/* docs/$SOURCE_BRANCH/

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'discordrb/webhooks/version'
Gem::Specification.new do |spec|
spec.name = 'discordrb-webhooks'
spec.version = Discordrb::Webhooks::VERSION
spec.authors = %w[meew0 swarley]
spec.email = ['']
spec.summary = 'Webhook client for discordrb'
spec.description = "A client for Discord's webhooks to fit alongside [discordrb](https://rubygems.org/gems/discordrb)."
spec.homepage = 'https://github.com/shardlab/discordrb'
spec.license = 'MIT'
spec.files = `git ls-files -z lib/discordrb/webhooks/`.split("\x0") + ['lib/discordrb/webhooks.rb']
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']
spec.add_dependency 'rest-client', '>= 2.0.0'
spec.required_ruby_version = '>= 2.5'
end

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'discordrb/version'
Gem::Specification.new do |spec|
spec.name = 'discordrb'
spec.version = Discordrb::VERSION
spec.authors = %w[meew0 swarley]
spec.email = ['']
spec.summary = 'Discord API for Ruby'
spec.description = 'A Ruby implementation of the Discord (https://discord.com) API.'
spec.homepage = 'https://github.com/shardlab/discordrb'
spec.license = 'MIT'
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|examples|lib/discordrb/webhooks)/}) }
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.metadata = {
'changelog_uri' => 'https://github.com/shardlab/discordrb/blob/master/CHANGELOG.md'
}
spec.require_paths = ['lib']
spec.add_dependency 'ffi', '>= 1.9.24'
spec.add_dependency 'opus-ruby'
spec.add_dependency 'rest-client', '>= 2.0.0'
spec.add_dependency 'websocket-client-simple', '>= 0.3.0'
spec.add_dependency 'discordrb-webhooks', '~> 3.3.0'
spec.required_ruby_version = '>= 2.5'
spec.add_development_dependency 'bundler', '>= 1.10', '< 3'
spec.add_development_dependency 'rake', '~> 13.0'
spec.add_development_dependency 'redcarpet', '~> 3.5.0' # YARD markdown formatting
spec.add_development_dependency 'rspec', '~> 3.10.0'
spec.add_development_dependency 'rspec-prof', '~> 0.0.7'
spec.add_development_dependency 'rubocop', '~> 1.4.0'
spec.add_development_dependency 'rubocop-performance', '~> 1.0'
spec.add_development_dependency 'simplecov', '~> 0.19.0'
spec.add_development_dependency 'yard', '~> 0.9.9'
end

View File

@ -0,0 +1,103 @@
# frozen_string_literal: true
require 'discordrb/version'
require 'discordrb/bot'
require 'discordrb/commands/command_bot'
require 'discordrb/logger'
# All discordrb functionality, to be extended by other files
module Discordrb
Thread.current[:discordrb_name] = 'main'
# The default debug logger used by discordrb.
LOGGER = Logger.new(ENV['DISCORDRB_FANCY_LOG'])
# The Unix timestamp Discord IDs are based on
DISCORD_EPOCH = 1_420_070_400_000
# Used to declare what events you wish to recieve from Discord.
# @see https://discordapp.com/developers/docs/topics/gateway#gateway-intents
INTENTS = {
servers: 1 << 0,
server_members: 1 << 1,
server_bans: 1 << 2,
server_emojis: 1 << 3,
server_integrations: 1 << 4,
server_webhooks: 1 << 5,
server_invites: 1 << 6,
server_voice_states: 1 << 7,
server_presences: 1 << 8,
server_messages: 1 << 9,
server_message_reactions: 1 << 10,
server_message_typing: 1 << 11,
direct_messages: 1 << 12,
direct_message_reactions: 1 << 13,
direct_message_typing: 1 << 14
}.freeze
# @return [Integer] All available intents
ALL_INTENTS = INTENTS.values.reduce(&:|)
# Compares two objects based on IDs - either the objects' IDs are equal, or one object is equal to the other's ID.
def self.id_compare(one_id, other)
other.respond_to?(:resolve_id) ? (one_id.resolve_id == other.resolve_id) : (one_id == other)
end
# The maximum length a Discord message can have
CHARACTER_LIMIT = 2000
# Splits a message into chunks of 2000 characters. Attempts to split by lines if possible.
# @param msg [String] The message to split.
# @return [Array<String>] the message split into chunks
def self.split_message(msg)
# If the messages is empty, return an empty array
return [] if msg.empty?
# Split the message into lines
lines = msg.lines
# Turn the message into a "triangle" of consecutively longer slices, for example the array [1,2,3,4] would become
# [
# [1],
# [1, 2],
# [1, 2, 3],
# [1, 2, 3, 4]
# ]
tri = (0...lines.length).map { |i| lines.combination(i + 1).first }
# Join the individual elements together to get an array of strings with consecutively more lines
joined = tri.map(&:join)
# Find the largest element that is still below the character limit, or if none such element exists return the first
ideal = joined.max_by { |e| e.length > CHARACTER_LIMIT ? -1 : e.length }
# If it's still larger than the character limit (none was smaller than it) split it into the largest chunk without
# cutting words apart, breaking on the nearest space within character limit, otherwise just return an array with one element
ideal_ary = ideal.length > CHARACTER_LIMIT ? ideal.split(/(.{1,#{CHARACTER_LIMIT}}\b|.{1,#{CHARACTER_LIMIT}})/o).reject(&:empty?) : [ideal]
# Slice off the ideal part and strip newlines
rest = msg[ideal.length..-1].strip
# If none remains, return an empty array -> we're done
return [] unless rest
# Otherwise, call the method recursively to split the rest of the string and add it onto the ideal array
ideal_ary + split_message(rest)
end
end
# In discordrb, Integer and {String} are monkey-patched to allow for easy resolution of IDs
class Integer
# @return [Integer] The Discord ID represented by this integer, i.e. the integer itself
def resolve_id
self
end
end
# In discordrb, {Integer} and String are monkey-patched to allow for easy resolution of IDs
class String
# @return [Integer] The Discord ID represented by this string, i.e. the string converted to an integer
def resolve_id
to_i
end
end

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
require 'discordrb/id_object'
module Discordrb
# Builder class for `allowed_mentions` when sending messages.
class AllowedMentions
# @return [Array<"users", "roles", "everyone">, nil]
attr_accessor :parse
# @return [Array<String, Integer>, nil]
attr_accessor :users
# @return [Array<String, Integer>, nil]
attr_accessor :roles
# @param parse [Array<"users", "roles", "everyone">] Mention types that can be inferred from the message.
# `users` and `roles` allow for all mentions of the respective type to ping. `everyone` allows usage of `@everyone` and `@here`
# @param users [Array<User, String, Integer>] Users or user IDs that can be pinged. Cannot be used in conjunction with `"users"` in `parse`
# @param roles [Array<Role, String, Integer>] Roles or role IDs that can be pinged. Cannot be used in conjunction with `"roles"` in `parse`
def initialize(parse: nil, users: nil, roles: nil)
@parse = parse
@users = users
@roles = roles
end
# @!visibility private
def to_hash
{
parse: @parse,
users: @users&.map { |user| user.is_a?(IDObject) ? user.id : user },
roles: @roles&.map { |role| role.is_a?(IDObject) ? role.id : role }
}.compact
end
end
end

View File

@ -0,0 +1,344 @@
# frozen_string_literal: true
require 'rest-client'
require 'json'
require 'time'
require 'discordrb/errors'
# List of methods representing endpoints in Discord's API
module Discordrb::API
# The base URL of the Discord REST API.
APIBASE = 'https://discord.com/api/v6'
# The URL of Discord's CDN
CDN_URL = 'https://cdn.discordapp.com'
module_function
# @return [String] the currently used API base URL.
def api_base
@api_base || APIBASE
end
# Sets the API base URL to something.
def api_base=(value)
@api_base = value
end
# @return [String] the currently used CDN url
def cdn_url
@cdn_url || CDN_URL
end
# @return [String] the bot name, previously specified using {.bot_name=}.
def bot_name
@bot_name
end
# Sets the bot name to something. Used in {.user_agent}. For the bot's username, see {Profile#username=}.
def bot_name=(value)
@bot_name = value
end
# Changes the rate limit tracing behaviour. If rate limit tracing is on, a full backtrace will be logged on every RL
# hit.
# @param value [true, false] whether or not to enable rate limit tracing
def trace=(value)
@trace = value
end
# Generate a user agent identifying this requester as discordrb.
def user_agent
# This particular string is required by the Discord devs.
required = "DiscordBot (https://github.com/shardlab/discordrb, v#{Discordrb::VERSION})"
@bot_name ||= ''
"#{required} rest-client/#{RestClient::VERSION} #{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL} discordrb/#{Discordrb::VERSION} #{@bot_name}"
end
# Resets all rate limit mutexes
def reset_mutexes
@mutexes = {}
@global_mutex = Mutex.new
end
# Wait a specified amount of time synchronised with the specified mutex.
def sync_wait(time, mutex)
mutex.synchronize { sleep time }
end
# Wait for a specified mutex to unlock and do nothing with it afterwards.
def mutex_wait(mutex)
mutex.lock
mutex.unlock
end
# Performs a RestClient request.
# @param type [Symbol] The type of HTTP request to use.
# @param attributes [Array] The attributes for the request.
def raw_request(type, attributes)
RestClient.send(type, *attributes)
rescue RestClient::Forbidden => e
# HACK: for #request, dynamically inject restclient's response into NoPermission - this allows us to rate limit
noprm = Discordrb::Errors::NoPermission.new
noprm.define_singleton_method(:_rc_response) { e.response }
raise noprm, "The bot doesn't have the required permission to do this!"
rescue RestClient::BadGateway
Discordrb::LOGGER.warn('Got a 502 while sending a request! Not a big deal, retrying the request')
retry
end
# Make an API request, including rate limit handling.
def request(key, major_parameter, type, *attributes)
# Add a custom user agent
attributes.last[:user_agent] = user_agent if attributes.last.is_a? Hash
# Specify RateLimit precision
attributes.last[:x_ratelimit_precision] = 'millisecond'
# The most recent Discord rate limit requirements require the support of major parameters, where a particular route
# and major parameter combination (*not* the HTTP method) uniquely identifies a RL bucket.
key = [key, major_parameter].freeze
begin
mutex = @mutexes[key] ||= Mutex.new
# Lock and unlock, i.e. wait for the mutex to unlock and don't do anything with it afterwards
mutex_wait(mutex)
# If the global mutex happens to be locked right now, wait for that as well.
mutex_wait(@global_mutex) if @global_mutex.locked?
response = nil
begin
response = raw_request(type, attributes)
rescue RestClient::Exception => e
response = e.response
raise e
rescue Discordrb::Errors::NoPermission => e
if e.respond_to?(:_rc_response)
response = e._rc_response
else
Discordrb::LOGGER.warn("NoPermission doesn't respond_to? _rc_response!")
end
raise e
ensure
if response
handle_preemptive_rl(response.headers, mutex, key) if response.headers[:x_ratelimit_remaining] == '0' && !mutex.locked?
else
Discordrb::LOGGER.ratelimit('Response was nil before trying to preemptively rate limit!')
end
end
rescue RestClient::TooManyRequests => e
# If the 429 is from the global RL, then we have to use the global mutex instead.
mutex = @global_mutex if e.response.headers[:x_ratelimit_global] == 'true'
unless mutex.locked?
response = JSON.parse(e.response)
wait_seconds = response['retry_after'].to_i / 1000.0
Discordrb::LOGGER.ratelimit("Locking RL mutex (key: #{key}) for #{wait_seconds} seconds due to Discord rate limiting")
trace("429 #{key.join(' ')}")
# Wait the required time synchronized by the mutex (so other incoming requests have to wait) but only do it if
# the mutex isn't locked already so it will only ever wait once
sync_wait(wait_seconds, mutex)
end
retry
end
response
end
# Handles pre-emptive rate limiting by waiting the given mutex by the difference of the Date header to the
# X-Ratelimit-Reset header, thus making sure we don't get 429'd in any subsequent requests.
def handle_preemptive_rl(headers, mutex, key)
Discordrb::LOGGER.ratelimit "RL bucket depletion detected! Date: #{headers[:date]} Reset: #{headers[:x_ratelimit_reset]}"
delta = headers[:x_ratelimit_reset_after].to_f
Discordrb::LOGGER.warn("Locking RL mutex (key: #{key}) for #{delta} seconds pre-emptively")
sync_wait(delta, mutex)
end
# Perform rate limit tracing. All this method does is log the current backtrace to the console with the `:ratelimit`
# level.
# @param reason [String] the reason to include with the backtrace.
def trace(reason)
unless @trace
Discordrb::LOGGER.debug("trace was called with reason #{reason}, but tracing is not enabled")
return
end
Discordrb::LOGGER.ratelimit("Trace (#{reason}):")
caller.each do |str|
Discordrb::LOGGER.ratelimit(" #{str}")
end
end
# Make an icon URL from server and icon IDs
def icon_url(server_id, icon_id, format = 'webp')
"#{cdn_url}/icons/#{server_id}/#{icon_id}.#{format}"
end
# Make an icon URL from application and icon IDs
def app_icon_url(app_id, icon_id, format = 'webp')
"#{cdn_url}/app-icons/#{app_id}/#{icon_id}.#{format}"
end
# Make a widget picture URL from server ID
def widget_url(server_id, style = 'shield')
"#{api_base}/guilds/#{server_id}/widget.png?style=#{style}"
end
# Make a splash URL from server and splash IDs
def splash_url(server_id, splash_id, format = 'webp')
"#{cdn_url}/splashes/#{server_id}/#{splash_id}.#{format}"
end
# Make a banner URL from server and banner IDs
def banner_url(server_id, banner_id, format = 'webp')
"#{cdn_url}/banners/#{server_id}/#{banner_id}.#{format}"
end
# Make an emoji icon URL from emoji ID
def emoji_icon_url(emoji_id, format = 'webp')
"#{cdn_url}/emojis/#{emoji_id}.#{format}"
end
# Make an asset URL from application and asset IDs
def asset_url(application_id, asset_id, format = 'webp')
"#{cdn_url}/app-assets/#{application_id}/#{asset_id}.#{format}"
end
# Make an achievement icon URL from application ID, achievement ID, and icon hash
def achievement_icon_url(application_id, achievement_id, icon_hash, format = 'webp')
"#{cdn_url}/app-assets/#{application_id}/achievements/#{achievement_id}/icons/#{icon_hash}.#{format}"
end
# Login to the server
def login(email, password)
request(
:auth_login,
nil,
:post,
"#{api_base}/auth/login",
email: email,
password: password
)
end
# Logout from the server
def logout(token)
request(
:auth_logout,
nil,
:post,
"#{api_base}/auth/logout",
nil,
Authorization: token
)
end
# Create an OAuth application
def create_oauth_application(token, name, redirect_uris)
request(
:oauth2_applications,
nil,
:post,
"#{api_base}/oauth2/applications",
{ name: name, redirect_uris: redirect_uris }.to_json,
Authorization: token,
content_type: :json
)
end
# Change an OAuth application's properties
def update_oauth_application(token, name, redirect_uris, description = '', icon = nil)
request(
:oauth2_applications,
nil,
:put,
"#{api_base}/oauth2/applications",
{ name: name, redirect_uris: redirect_uris, description: description, icon: icon }.to_json,
Authorization: token,
content_type: :json
)
end
# Get the bot's OAuth application's information
def oauth_application(token)
request(
:oauth2_applications_me,
nil,
:get,
"#{api_base}/oauth2/applications/@me",
Authorization: token
)
end
# Acknowledge that a message has been received
# The last acknowledged message will be sent in the ready packet,
# so this is an easy way to catch up on messages
def acknowledge_message(token, channel_id, message_id)
request(
:channels_cid_messages_mid_ack,
nil, # This endpoint is unavailable for bot accounts and thus isn't subject to its rate limit requirements.
:post,
"#{api_base}/channels/#{channel_id}/messages/#{message_id}/ack",
nil,
Authorization: token
)
end
# Get the gateway to be used
def gateway(token)
request(
:gateway,
nil,
:get,
"#{api_base}/gateway",
Authorization: token
)
end
# Get the gateway to be used, with additional information for sharding and
# session start limits
def gateway_bot(token)
request(
:gateway_bot,
nil,
:get,
"#{api_base}/gateway/bot",
Authorization: token
)
end
# Validate a token (this request will fail if the token is invalid)
def validate_token(token)
request(
:auth_login,
nil,
:post,
"#{api_base}/auth/login",
{}.to_json,
Authorization: token,
content_type: :json
)
end
# Get a list of available voice regions
def voice_regions(token)
request(
:voice_regions,
nil,
:get,
"#{api_base}/voice/regions",
Authorization: token,
content_type: :json
)
end
end
Discordrb::API.reset_mutexes

View File

@ -0,0 +1,428 @@
# frozen_string_literal: true
# API calls for Channel
module Discordrb::API::Channel
module_function
# Get a channel's data
# https://discord.com/developers/docs/resources/channel#get-channel
def resolve(token, channel_id)
Discordrb::API.request(
:channels_cid,
channel_id,
:get,
"#{Discordrb::API.api_base}/channels/#{channel_id}",
Authorization: token
)
end
# Update a channel's data
# https://discord.com/developers/docs/resources/channel#modify-channel
def update(token, channel_id, name, topic, position, bitrate, user_limit, nsfw, permission_overwrites = nil, parent_id = nil, rate_limit_per_user = nil, reason = nil)
data = { name: name, position: position, topic: topic, bitrate: bitrate, user_limit: user_limit, nsfw: nsfw, parent_id: parent_id, rate_limit_per_user: rate_limit_per_user }
data[:permission_overwrites] = permission_overwrites unless permission_overwrites.nil?
Discordrb::API.request(
:channels_cid,
channel_id,
:patch,
"#{Discordrb::API.api_base}/channels/#{channel_id}",
data.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Delete a channel
# https://discord.com/developers/docs/resources/channel#deleteclose-channel
def delete(token, channel_id, reason = nil)
Discordrb::API.request(
:channels_cid,
channel_id,
:delete,
"#{Discordrb::API.api_base}/channels/#{channel_id}",
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Get a list of messages from a channel's history
# https://discord.com/developers/docs/resources/channel#get-channel-messages
def messages(token, channel_id, amount, before = nil, after = nil, around = nil)
Discordrb::API.request(
:channels_cid_messages,
channel_id,
:get,
"#{Discordrb::API.api_base}/channels/#{channel_id}/messages?limit=#{amount}#{"&before=#{before}" if before}#{"&after=#{after}" if after}#{"&around=#{around}" if around}",
Authorization: token
)
end
# Get a single message from a channel's history by id
# https://discord.com/developers/docs/resources/channel#get-channel-message
def message(token, channel_id, message_id)
Discordrb::API.request(
:channels_cid_messages_mid,
channel_id,
:get,
"#{Discordrb::API.api_base}/channels/#{channel_id}/messages/#{message_id}",
Authorization: token
)
end
# Send a message to a channel
# https://discordapp.com/developers/docs/resources/channel#create-message
# @param attachments [Array<File>, nil] Attachments to use with `attachment://` in embeds. See
# https://discord.com/developers/docs/resources/channel#create-message-using-attachments-within-embeds
def create_message(token, channel_id, message, tts = false, embed = nil, nonce = nil, attachments = nil, allowed_mentions = nil, message_reference = nil)
body = { content: message, tts: tts, embed: embed, nonce: nonce, allowed_mentions: allowed_mentions, message_reference: message_reference }
body = if attachments
files = [*0...attachments.size].zip(attachments).to_h
{ **files, payload_json: body.to_json }
else
body.to_json
end
headers = { Authorization: token }
headers[:content_type] = :json unless attachments
Discordrb::API.request(
:channels_cid_messages_mid,
channel_id,
:post,
"#{Discordrb::API.api_base}/channels/#{channel_id}/messages",
body,
**headers
)
rescue RestClient::BadRequest => e
parsed = JSON.parse(e.response.body)
raise Discordrb::Errors::MessageTooLong, "Message over the character limit (#{message.length} > 2000)" if parsed['content'].is_a?(Array) && parsed['content'].first == 'Must be 2000 or fewer in length.'
raise
end
# Send a file as a message to a channel
# https://discord.com/developers/docs/resources/channel#upload-file
def upload_file(token, channel_id, file, caption: nil, tts: false)
Discordrb::API.request(
:channels_cid_messages_mid,
channel_id,
:post,
"#{Discordrb::API.api_base}/channels/#{channel_id}/messages",
{ file: file, content: caption, tts: tts },
Authorization: token
)
end
# Edit a message
# https://discord.com/developers/docs/resources/channel#edit-message
def edit_message(token, channel_id, message_id, message, mentions = [], embed = nil)
Discordrb::API.request(
:channels_cid_messages_mid,
channel_id,
:patch,
"#{Discordrb::API.api_base}/channels/#{channel_id}/messages/#{message_id}",
{ content: message, mentions: mentions, embed: embed }.to_json,
Authorization: token,
content_type: :json
)
end
# Delete a message
# https://discordapp.com/developers/docs/resources/channel#delete-message
def delete_message(token, channel_id, message_id, reason = nil)
Discordrb::API.request(
:channels_cid_messages_mid,
channel_id,
:delete,
"#{Discordrb::API.api_base}/channels/#{channel_id}/messages/#{message_id}",
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Delete messages in bulk
# https://discordapp.com/developers/docs/resources/channel#bulk-delete-messages
def bulk_delete_messages(token, channel_id, messages = [], reason = nil)
Discordrb::API.request(
:channels_cid_messages_bulk_delete,
channel_id,
:post,
"#{Discordrb::API.api_base}/channels/#{channel_id}/messages/bulk-delete",
{ messages: messages }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Create a reaction on a message using this client
# https://discord.com/developers/docs/resources/channel#create-reaction
def create_reaction(token, channel_id, message_id, emoji)
emoji = URI.encode_www_form_component(emoji) unless emoji.ascii_only?
Discordrb::API.request(
:channels_cid_messages_mid_reactions_emoji_me,
channel_id,
:put,
"#{Discordrb::API.api_base}/channels/#{channel_id}/messages/#{message_id}/reactions/#{emoji}/@me",
nil,
Authorization: token,
content_type: :json
)
end
# Delete this client's own reaction on a message
# https://discord.com/developers/docs/resources/channel#delete-own-reaction
def delete_own_reaction(token, channel_id, message_id, emoji)
emoji = URI.encode_www_form_component(emoji) unless emoji.ascii_only?
Discordrb::API.request(
:channels_cid_messages_mid_reactions_emoji_me,
channel_id,
:delete,
"#{Discordrb::API.api_base}/channels/#{channel_id}/messages/#{message_id}/reactions/#{emoji}/@me",
Authorization: token
)
end
# Delete another client's reaction on a message
# https://discord.com/developers/docs/resources/channel#delete-user-reaction
def delete_user_reaction(token, channel_id, message_id, emoji, user_id)
emoji = URI.encode_www_form_component(emoji) unless emoji.ascii_only?
Discordrb::API.request(
:channels_cid_messages_mid_reactions_emoji_uid,
channel_id,
:delete,
"#{Discordrb::API.api_base}/channels/#{channel_id}/messages/#{message_id}/reactions/#{emoji}/#{user_id}",
Authorization: token
)
end
# Get a list of clients who reacted with a specific reaction on a message
# https://discord.com/developers/docs/resources/channel#get-reactions
def get_reactions(token, channel_id, message_id, emoji, before_id, after_id, limit = 100)
emoji = URI.encode_www_form_component(emoji) unless emoji.ascii_only?
query_string = "limit=#{limit}#{"&before=#{before_id}" if before_id}#{"&after=#{after_id}" if after_id}"
Discordrb::API.request(
:channels_cid_messages_mid_reactions_emoji,
channel_id,
:get,
"#{Discordrb::API.api_base}/channels/#{channel_id}/messages/#{message_id}/reactions/#{emoji}?#{query_string}",
Authorization: token
)
end
# Deletes all reactions on a message from all clients
# https://discord.com/developers/docs/resources/channel#delete-all-reactions
def delete_all_reactions(token, channel_id, message_id)
Discordrb::API.request(
:channels_cid_messages_mid_reactions,
channel_id,
:delete,
"#{Discordrb::API.api_base}/channels/#{channel_id}/messages/#{message_id}/reactions",
Authorization: token
)
end
# Update a channels permission for a role or member
# https://discord.com/developers/docs/resources/channel#edit-channel-permissions
def update_permission(token, channel_id, overwrite_id, allow, deny, type, reason = nil)
Discordrb::API.request(
:channels_cid_permissions_oid,
channel_id,
:put,
"#{Discordrb::API.api_base}/channels/#{channel_id}/permissions/#{overwrite_id}",
{ type: type, id: overwrite_id, allow: allow, deny: deny }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Get a channel's invite list
# https://discord.com/developers/docs/resources/channel#get-channel-invites
def invites(token, channel_id)
Discordrb::API.request(
:channels_cid_invites,
channel_id,
:get,
"#{Discordrb::API.api_base}/channels/#{channel_id}/invites",
Authorization: token
)
end
# Create an instant invite from a server or a channel id
# https://discord.com/developers/docs/resources/channel#create-channel-invite
def create_invite(token, channel_id, max_age = 0, max_uses = 0, temporary = false, unique = false, reason = nil)
Discordrb::API.request(
:channels_cid_invites,
channel_id,
:post,
"#{Discordrb::API.api_base}/channels/#{channel_id}/invites",
{ max_age: max_age, max_uses: max_uses, temporary: temporary, unique: unique }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Delete channel permission
# https://discord.com/developers/docs/resources/channel#delete-channel-permission
def delete_permission(token, channel_id, overwrite_id, reason = nil)
Discordrb::API.request(
:channels_cid_permissions_oid,
channel_id,
:delete,
"#{Discordrb::API.api_base}/channels/#{channel_id}/permissions/#{overwrite_id}",
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Start typing (needs to be resent every 5 seconds to keep up the typing)
# https://discord.com/developers/docs/resources/channel#trigger-typing-indicator
def start_typing(token, channel_id)
Discordrb::API.request(
:channels_cid_typing,
channel_id,
:post,
"#{Discordrb::API.api_base}/channels/#{channel_id}/typing",
nil,
Authorization: token
)
end
# Get a list of pinned messages in a channel
# https://discord.com/developers/docs/resources/channel#get-pinned-messages
def pinned_messages(token, channel_id)
Discordrb::API.request(
:channels_cid_pins,
channel_id,
:get,
"#{Discordrb::API.api_base}/channels/#{channel_id}/pins",
Authorization: token
)
end
# Pin a message
# https://discordapp.com/developers/docs/resources/channel#add-pinned-channel-message
def pin_message(token, channel_id, message_id, reason = nil)
Discordrb::API.request(
:channels_cid_pins_mid,
channel_id,
:put,
"#{Discordrb::API.api_base}/channels/#{channel_id}/pins/#{message_id}",
nil,
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Unpin a message
# https://discordapp.com/developers/docs/resources/channel#delete-pinned-channel-message
def unpin_message(token, channel_id, message_id, reason = nil)
Discordrb::API.request(
:channels_cid_pins_mid,
channel_id,
:delete,
"#{Discordrb::API.api_base}/channels/#{channel_id}/pins/#{message_id}",
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Create an empty group channel.
def create_empty_group(token, bot_user_id)
Discordrb::API.request(
:users_uid_channels,
nil,
:post,
"#{Discordrb::API.api_base}/users/#{bot_user_id}/channels",
{}.to_json,
Authorization: token,
content_type: :json
)
end
# Create a group channel.
def create_group(token, pm_channel_id, user_id)
Discordrb::API.request(
:channels_cid_recipients_uid,
nil,
:put,
"#{Discordrb::API.api_base}/channels/#{pm_channel_id}/recipients/#{user_id}",
{}.to_json,
Authorization: token,
content_type: :json
)
rescue RestClient::InternalServerError
raise 'Attempted to add self as a new group channel recipient!'
rescue RestClient::NoContent
raise 'Attempted to create a group channel with the PM channel recipient!'
rescue RestClient::Forbidden
raise 'Attempted to add a user to group channel without permission!'
end
# Add a user to a group channel.
def add_group_user(token, group_channel_id, user_id)
Discordrb::API.request(
:channels_cid_recipients_uid,
nil,
:put,
"#{Discordrb::API.api_base}/channels/#{group_channel_id}/recipients/#{user_id}",
{}.to_json,
Authorization: token,
content_type: :json
)
end
# Remove a user from a group channel.
def remove_group_user(token, group_channel_id, user_id)
Discordrb::API.request(
:channels_cid_recipients_uid,
nil,
:delete,
"#{Discordrb::API.api_base}/channels/#{group_channel_id}/recipients/#{user_id}",
Authorization: token,
content_type: :json
)
end
# Leave a group channel.
def leave_group(token, group_channel_id)
Discordrb::API.request(
:channels_cid,
nil,
:delete,
"#{Discordrb::API.api_base}/channels/#{group_channel_id}",
Authorization: token,
content_type: :json
)
end
# Create a webhook
# https://discord.com/developers/docs/resources/webhook#create-webhook
def create_webhook(token, channel_id, name, avatar = nil, reason = nil)
Discordrb::API.request(
:channels_cid_webhooks,
channel_id,
:post,
"#{Discordrb::API.api_base}/channels/#{channel_id}/webhooks",
{ name: name, avatar: avatar }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Get channel webhooks
# https://discord.com/developers/docs/resources/webhook#get-channel-webhooks
def webhooks(token, channel_id)
Discordrb::API.request(
:channels_cid_webhooks,
channel_id,
:get,
"#{Discordrb::API.api_base}/channels/#{channel_id}/webhooks",
Authorization: token
)
end
end

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
# API calls for Invite object
module Discordrb::API::Invite
module_function
# Resolve an invite
# https://discord.com/developers/docs/resources/invite#get-invite
def resolve(token, invite_code, counts = true)
Discordrb::API.request(
:invite_code,
nil,
:get,
"#{Discordrb::API.api_base}/invite/#{invite_code}#{counts ? '?with_counts=true' : ''}",
Authorization: token
)
end
# Delete an invite by code
# https://discord.com/developers/docs/resources/invite#delete-invite
def delete(token, code, reason = nil)
Discordrb::API.request(
:invites_code,
nil,
:delete,
"#{Discordrb::API.api_base}/invites/#{code}",
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Join a server using an invite
# https://discord.com/developers/docs/resources/invite#accept-invite
def accept(token, invite_code)
Discordrb::API.request(
:invite_code,
nil,
:post,
"#{Discordrb::API.api_base}/invite/#{invite_code}",
nil,
Authorization: token
)
end
end

View File

@ -0,0 +1,536 @@
# frozen_string_literal: true
# API calls for Server
module Discordrb::API::Server
module_function
# Create a server
# https://discord.com/developers/docs/resources/guild#create-guild
def create(token, name, region = :'eu-central')
Discordrb::API.request(
:guilds,
nil,
:post,
"#{Discordrb::API.api_base}/guilds",
{ name: name, region: region.to_s }.to_json,
Authorization: token,
content_type: :json
)
end
# Get a server's data
# https://discord.com/developers/docs/resources/guild#get-guild
def resolve(token, server_id, with_counts = nil)
Discordrb::API.request(
:guilds_sid,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}#{'?with_counts=true' if with_counts}",
Authorization: token
)
end
# Update a server
# https://discord.com/developers/docs/resources/guild#modify-guild
def update(token, server_id, name, region, icon, afk_channel_id, afk_timeout, splash, default_message_notifications, verification_level, explicit_content_filter, system_channel_id, reason = nil)
Discordrb::API.request(
:guilds_sid,
server_id,
:patch,
"#{Discordrb::API.api_base}/guilds/#{server_id}",
{ name: name, region: region, icon: icon, afk_channel_id: afk_channel_id, afk_timeout: afk_timeout, splash: splash, default_message_notifications: default_message_notifications, verification_level: verification_level, explicit_content_filter: explicit_content_filter, system_channel_id: system_channel_id }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Transfer server ownership
def transfer_ownership(token, server_id, user_id, reason = nil)
Discordrb::API.request(
:guilds_sid,
server_id,
:patch,
"#{Discordrb::API.api_base}/guilds/#{server_id}",
{ owner_id: user_id }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Delete a server
# https://discord.com/developers/docs/resources/guild#delete-guild
def delete(token, server_id)
Discordrb::API.request(
:guilds_sid,
server_id,
:delete,
"#{Discordrb::API.api_base}/guilds/#{server_id}",
Authorization: token
)
end
# Get a server's channels list
# https://discord.com/developers/docs/resources/guild#get-guild-channels
def channels(token, server_id)
Discordrb::API.request(
:guilds_sid_channels,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}/channels",
Authorization: token
)
end
# Create a channel
# https://discord.com/developers/docs/resources/guild#create-guild-channel
def create_channel(token, server_id, name, type, topic, bitrate, user_limit, permission_overwrites, parent_id, nsfw, rate_limit_per_user, position, reason = nil)
Discordrb::API.request(
:guilds_sid_channels,
server_id,
:post,
"#{Discordrb::API.api_base}/guilds/#{server_id}/channels",
{ name: name, type: type, topic: topic, bitrate: bitrate, user_limit: user_limit, permission_overwrites: permission_overwrites, parent_id: parent_id, nsfw: nsfw, rate_limit_per_user: rate_limit_per_user, position: position }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Update a channels position
# https://discord.com/developers/docs/resources/guild#modify-guild-channel-positions
def update_channel_positions(token, server_id, positions)
Discordrb::API.request(
:guilds_sid_channels,
server_id,
:patch,
"#{Discordrb::API.api_base}/guilds/#{server_id}/channels",
positions.to_json,
Authorization: token,
content_type: :json
)
end
# Get a member's data
# https://discord.com/developers/docs/resources/guild#get-guild-member
def resolve_member(token, server_id, user_id)
Discordrb::API.request(
:guilds_sid_members_uid,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}/members/#{user_id}",
Authorization: token
)
end
# Gets members from the server
# https://discord.com/developers/docs/resources/guild#list-guild-members
def resolve_members(token, server_id, limit, after = nil)
Discordrb::API.request(
:guilds_sid_members,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}/members?limit=#{limit}#{"&after=#{after}" if after}",
Authorization: token
)
end
# Update a user properties
# https://discord.com/developers/docs/resources/guild#modify-guild-member
def update_member(token, server_id, user_id, nick: nil, roles: nil, mute: nil, deaf: nil, channel_id: nil, reason: nil)
Discordrb::API.request(
:guilds_sid_members_uid,
server_id,
:patch,
"#{Discordrb::API.api_base}/guilds/#{server_id}/members/#{user_id}", {
roles: roles,
nick: nick,
mute: mute,
deaf: deaf,
channel_id: channel_id
}.compact.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Remove user from server
# https://discord.com/developers/docs/resources/guild#remove-guild-member
def remove_member(token, server_id, user_id, reason = nil)
Discordrb::API.request(
:guilds_sid_members_uid,
server_id,
:delete,
"#{Discordrb::API.api_base}/guilds/#{server_id}/members/#{user_id}",
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Get a server's banned users
# https://discord.com/developers/docs/resources/guild#get-guild-bans
def bans(token, server_id)
Discordrb::API.request(
:guilds_sid_bans,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}/bans",
Authorization: token
)
end
# Ban a user from a server and delete their messages from the last message_days days
# https://discord.com/developers/docs/resources/guild#create-guild-ban
def ban_user(token, server_id, user_id, message_days, reason = nil)
reason = URI.encode_www_form_component(reason) if reason
Discordrb::API.request(
:guilds_sid_bans_uid,
server_id,
:put,
"#{Discordrb::API.api_base}/guilds/#{server_id}/bans/#{user_id}?delete-message-days=#{message_days}&reason=#{reason}",
nil,
Authorization: token
)
end
# Unban a user from a server
# https://discord.com/developers/docs/resources/guild#remove-guild-ban
def unban_user(token, server_id, user_id, reason = nil)
Discordrb::API.request(
:guilds_sid_bans_uid,
server_id,
:delete,
"#{Discordrb::API.api_base}/guilds/#{server_id}/bans/#{user_id}",
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Get server roles
# https://discord.com/developers/docs/resources/guild#get-guild-roles
def roles(token, server_id)
Discordrb::API.request(
:guilds_sid_roles,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}/roles",
Authorization: token
)
end
# Create a role (parameters such as name and colour if not set can be set by update_role afterwards)
# Permissions are the Discord defaults; allowed: invite creation, reading/sending messages,
# sending TTS messages, embedding links, sending files, reading the history, mentioning everybody,
# connecting to voice, speaking and voice activity (push-to-talk isn't mandatory)
# https://discord.com/developers/docs/resources/guild#get-guild-roles
def create_role(token, server_id, name, colour, hoist, mentionable, packed_permissions, reason = nil)
Discordrb::API.request(
:guilds_sid_roles,
server_id,
:post,
"#{Discordrb::API.api_base}/guilds/#{server_id}/roles",
{ color: colour, name: name, hoist: hoist, mentionable: mentionable, permissions: packed_permissions }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Update a role
# Permissions are the Discord defaults; allowed: invite creation, reading/sending messages,
# sending TTS messages, embedding links, sending files, reading the history, mentioning everybody,
# connecting to voice, speaking and voice activity (push-to-talk isn't mandatory)
# https://discord.com/developers/docs/resources/guild#batch-modify-guild-role
def update_role(token, server_id, role_id, name, colour, hoist = false, mentionable = false, packed_permissions = 104_324_161, reason = nil)
Discordrb::API.request(
:guilds_sid_roles_rid,
server_id,
:patch,
"#{Discordrb::API.api_base}/guilds/#{server_id}/roles/#{role_id}",
{ color: colour, name: name, hoist: hoist, mentionable: mentionable, permissions: packed_permissions }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Update role positions
# https://discord.com/developers/docs/resources/guild#modify-guild-role-positions
def update_role_positions(token, server_id, roles)
Discordrb::API.request(
:guilds_sid_roles,
server_id,
:patch,
"#{Discordrb::API.api_base}/guilds/#{server_id}/roles",
roles.to_json,
Authorization: token,
content_type: :json
)
end
# Delete a role
# https://discord.com/developers/docs/resources/guild#delete-guild-role
def delete_role(token, server_id, role_id, reason = nil)
Discordrb::API.request(
:guilds_sid_roles_rid,
server_id,
:delete,
"#{Discordrb::API.api_base}/guilds/#{server_id}/roles/#{role_id}",
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Adds a single role to a member
# https://discord.com/developers/docs/resources/guild#add-guild-member-role
def add_member_role(token, server_id, user_id, role_id, reason = nil)
Discordrb::API.request(
:guilds_sid_members_uid_roles_rid,
server_id,
:put,
"#{Discordrb::API.api_base}/guilds/#{server_id}/members/#{user_id}/roles/#{role_id}",
nil,
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Removes a single role from a member
# https://discord.com/developers/docs/resources/guild#remove-guild-member-role
def remove_member_role(token, server_id, user_id, role_id, reason = nil)
Discordrb::API.request(
:guilds_sid_members_uid_roles_rid,
server_id,
:delete,
"#{Discordrb::API.api_base}/guilds/#{server_id}/members/#{user_id}/roles/#{role_id}",
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Get server prune count
# https://discord.com/developers/docs/resources/guild#get-guild-prune-count
def prune_count(token, server_id, days)
Discordrb::API.request(
:guilds_sid_prune,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}/prune?days=#{days}",
Authorization: token
)
end
# Begin server prune
# https://discord.com/developers/docs/resources/guild#begin-guild-prune
def begin_prune(token, server_id, days, reason = nil)
Discordrb::API.request(
:guilds_sid_prune,
server_id,
:post,
"#{Discordrb::API.api_base}/guilds/#{server_id}/prune",
{ days: days },
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Get invites from server
# https://discord.com/developers/docs/resources/guild#get-guild-invites
def invites(token, server_id)
Discordrb::API.request(
:guilds_sid_invites,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}/invites",
Authorization: token
)
end
# Gets a server's audit logs
# https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log
def audit_logs(token, server_id, limit, user_id = nil, action_type = nil, before = nil)
Discordrb::API.request(
:guilds_sid_auditlogs,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}/audit-logs?limit=#{limit}#{"&user_id=#{user_id}" if user_id}#{"&action_type=#{action_type}" if action_type}#{"&before=#{before}" if before}",
Authorization: token
)
end
# Get server integrations
# https://discord.com/developers/docs/resources/guild#get-guild-integrations
def integrations(token, server_id)
Discordrb::API.request(
:guilds_sid_integrations,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}/integrations",
Authorization: token
)
end
# Create a server integration
# https://discordapp.com/developers/docs/resources/guild#create-guild-integration
def create_integration(token, server_id, type, id, reason = nil)
Discordrb::API.request(
:guilds_sid_integrations,
server_id,
:post,
"#{Discordrb::API.api_base}/guilds/#{server_id}/integrations",
{ type: type, id: id },
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Update integration from server
# https://discord.com/developers/docs/resources/guild#modify-guild-integration
def update_integration(token, server_id, integration_id, expire_behavior, expire_grace_period, enable_emoticons)
Discordrb::API.request(
:guilds_sid_integrations_iid,
server_id,
:patch,
"#{Discordrb::API.api_base}/guilds/#{server_id}/integrations/#{integration_id}",
{ expire_behavior: expire_behavior, expire_grace_period: expire_grace_period, enable_emoticons: enable_emoticons }.to_json,
Authorization: token,
content_type: :json
)
end
# Delete a server integration
# https://discordapp.com/developers/docs/resources/guild#delete-guild-integration
def delete_integration(token, server_id, integration_id, reason = nil)
Discordrb::API.request(
:guilds_sid_integrations_iid,
server_id,
:delete,
"#{Discordrb::API.api_base}/guilds/#{server_id}/integrations/#{integration_id}",
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Sync an integration
# https://discord.com/developers/docs/resources/guild#sync-guild-integration
def sync_integration(token, server_id, integration_id)
Discordrb::API.request(
:guilds_sid_integrations_iid_sync,
server_id,
:post,
"#{Discordrb::API.api_base}/guilds/#{server_id}/integrations/#{integration_id}/sync",
nil,
Authorization: token
)
end
# Retrieves a server's embed information
# https://discord.com/developers/docs/resources/guild#get-guild-embed
def embed(token, server_id)
Discordrb::API.request(
:guilds_sid_embed,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}/embed",
Authorization: token
)
end
# Modify a server's embed settings
# https://discord.com/developers/docs/resources/guild#modify-guild-embed
def modify_embed(token, server_id, enabled, channel_id, reason = nil)
Discordrb::API.request(
:guilds_sid_embed,
server_id,
:patch,
"#{Discordrb::API.api_base}/guilds/#{server_id}/embed",
{ enabled: enabled, channel_id: channel_id }.to_json,
Authorization: token,
'X-Audit-Log-Reason': reason,
content_type: :json
)
end
# Adds a custom emoji.
# https://discord.com/developers/docs/resources/emoji#create-guild-emoji
def add_emoji(token, server_id, image, name, roles = [], reason = nil)
Discordrb::API.request(
:guilds_sid_emojis,
server_id,
:post,
"#{Discordrb::API.api_base}/guilds/#{server_id}/emojis",
{ image: image, name: name, roles: roles }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Changes an emoji name and/or roles.
# https://discord.com/developers/docs/resources/emoji#modify-guild-emoji
def edit_emoji(token, server_id, emoji_id, name, roles = nil, reason = nil)
Discordrb::API.request(
:guilds_sid_emojis_eid,
server_id,
:patch,
"#{Discordrb::API.api_base}/guilds/#{server_id}/emojis/#{emoji_id}",
{ name: name, roles: roles }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Deletes a custom emoji
# https://discord.com/developers/docs/resources/emoji#delete-guild-emoji
def delete_emoji(token, server_id, emoji_id, reason = nil)
Discordrb::API.request(
:guilds_sid_emojis_eid,
server_id,
:delete,
"#{Discordrb::API.api_base}/guilds/#{server_id}/emojis/#{emoji_id}",
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Available voice regions for this server
def regions(token, server_id)
Discordrb::API.request(
:guilds_sid_regions,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}/regions",
Authorization: token
)
end
# Get server webhooks
# https://discord.com/developers/docs/resources/webhook#get-guild-webhooks
def webhooks(token, server_id)
Discordrb::API.request(
:guilds_sid_webhooks,
server_id,
:get,
"#{Discordrb::API.api_base}/guilds/#{server_id}/webhooks",
Authorization: token
)
end
# Adds a member to a server with an OAuth2 Bearer token that has been granted `guilds.join`
# https://discord.com/developers/docs/resources/guild#add-guild-member
def add_member(token, server_id, user_id, access_token, nick = nil, roles = [], mute = false, deaf = false)
Discordrb::API.request(
:guilds_sid_members_uid,
server_id,
:put,
"#{Discordrb::API.api_base}/guilds/#{server_id}/members/#{user_id}",
{ access_token: access_token, nick: nick, roles: roles, mute: mute, deaf: deaf }.to_json,
content_type: :json,
Authorization: token
)
end
end

View File

@ -0,0 +1,149 @@
# frozen_string_literal: true
# API calls for User object
module Discordrb::API::User
module_function
# Get user data
# https://discord.com/developers/docs/resources/user#get-user
def resolve(token, user_id)
Discordrb::API.request(
:users_uid,
nil,
:get,
"#{Discordrb::API.api_base}/users/#{user_id}",
Authorization: token
)
end
# Get profile data
# https://discord.com/developers/docs/resources/user#get-current-user
def profile(token)
Discordrb::API.request(
:users_me,
nil,
:get,
"#{Discordrb::API.api_base}/users/@me",
Authorization: token
)
end
# Change the current bot's nickname on a server
def change_own_nickname(token, server_id, nick, reason = nil)
Discordrb::API.request(
:guilds_sid_members_me_nick,
server_id, # This is technically a guild endpoint
:patch,
"#{Discordrb::API.api_base}/guilds/#{server_id}/members/@me/nick",
{ nick: nick }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Update user data
# https://discord.com/developers/docs/resources/user#modify-current-user
def update_profile(token, email, password, new_username, avatar, new_password = nil)
Discordrb::API.request(
:users_me,
nil,
:patch,
"#{Discordrb::API.api_base}/users/@me",
{ avatar: avatar, email: email, new_password: new_password, password: password, username: new_username }.to_json,
Authorization: token,
content_type: :json
)
end
# Get the servers a user is connected to
# https://discord.com/developers/docs/resources/user#get-current-user-guilds
def servers(token)
Discordrb::API.request(
:users_me_guilds,
nil,
:get,
"#{Discordrb::API.api_base}/users/@me/guilds",
Authorization: token
)
end
# Leave a server
# https://discord.com/developers/docs/resources/user#leave-guild
def leave_server(token, server_id)
Discordrb::API.request(
:users_me_guilds_sid,
nil,
:delete,
"#{Discordrb::API.api_base}/users/@me/guilds/#{server_id}",
Authorization: token
)
end
# Get the DMs for the current user
# https://discord.com/developers/docs/resources/user#get-user-dms
def user_dms(token)
Discordrb::API.request(
:users_me_channels,
nil,
:get,
"#{Discordrb::API.api_base}/users/@me/channels",
Authorization: token
)
end
# Create a DM to another user
# https://discord.com/developers/docs/resources/user#create-dm
def create_pm(token, recipient_id)
Discordrb::API.request(
:users_me_channels,
nil,
:post,
"#{Discordrb::API.api_base}/users/@me/channels",
{ recipient_id: recipient_id }.to_json,
Authorization: token,
content_type: :json
)
end
# Get information about a user's connections
# https://discord.com/developers/docs/resources/user#get-users-connections
def connections(token)
Discordrb::API.request(
:users_me_connections,
nil,
:get,
"#{Discordrb::API.api_base}/users/@me/connections",
Authorization: token
)
end
# Change user status setting
def change_status_setting(token, status)
Discordrb::API.request(
:users_me_settings,
nil,
:patch,
"#{Discordrb::API.api_base}/users/@me/settings",
{ status: status }.to_json,
Authorization: token,
content_type: :json
)
end
# Returns one of the "default" discord avatars from the CDN given a discriminator
def default_avatar(discrim = 0)
index = discrim.to_i % 5
"#{Discordrb::API.cdn_url}/embed/avatars/#{index}.png"
end
# Make an avatar URL from the user and avatar IDs
def avatar_url(user_id, avatar_id, format = nil)
format ||= if avatar_id.start_with?('a_')
'gif'
else
'webp'
end
"#{Discordrb::API.cdn_url}/avatars/#{user_id}/#{avatar_id}.#{format}"
end
end

View File

@ -0,0 +1,83 @@
# frozen_string_literal: true
# API calls for Webhook object
module Discordrb::API::Webhook
module_function
# Get a webhook
# https://discord.com/developers/docs/resources/webhook#get-webhook
def webhook(token, webhook_id)
Discordrb::API.request(
:webhooks_wid,
nil,
:get,
"#{Discordrb::API.api_base}/webhooks/#{webhook_id}",
Authorization: token
)
end
# Get a webhook via webhook token
# https://discord.com/developers/docs/resources/webhook#get-webhook-with-token
def token_webhook(webhook_token, webhook_id)
Discordrb::API.request(
:webhooks_wid,
nil,
:get,
"#{Discordrb::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}"
)
end
# Update a webhook
# https://discord.com/developers/docs/resources/webhook#modify-webhook
def update_webhook(token, webhook_id, data, reason = nil)
Discordrb::API.request(
:webhooks_wid,
webhook_id,
:patch,
"#{Discordrb::API.api_base}/webhooks/#{webhook_id}",
data.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Update a webhook via webhook token
# https://discord.com/developers/docs/resources/webhook#modify-webhook-with-token
def token_update_webhook(webhook_token, webhook_id, data, reason = nil)
Discordrb::API.request(
:webhooks_wid,
webhook_id,
:patch,
"#{Discordrb::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}",
data.to_json,
content_type: :json,
'X-Audit-Log-Reason': reason
)
end
# Deletes a webhook
# https://discord.com/developers/docs/resources/webhook#delete-webhook
def delete_webhook(token, webhook_id, reason = nil)
Discordrb::API.request(
:webhooks_wid,
webhook_id,
:delete,
"#{Discordrb::API.api_base}/webhooks/#{webhook_id}",
Authorization: token,
'X-Audit-Log-Reason': reason
)
end
# Deletes a webhook via webhook token
# https://discord.com/developers/docs/resources/webhook#delete-webhook-with-token
def token_delete_webhook(webhook_token, webhook_id, reason = nil)
Discordrb::API.request(
:webhooks_wid,
webhook_id,
:delete,
"#{Discordrb::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}",
'X-Audit-Log-Reason': reason
)
end
end

Some files were not shown because too many files have changed in this diff Show More