lyrics 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +45 -0
  6. data/VERSION +1 -0
  7. data/bin/lyrics +66 -0
  8. data/lib/lyrics.rb +4 -0
  9. data/lib/lyrics/cli/application.rb +99 -0
  10. data/lib/lyrics/cli/optionsparser.rb +228 -0
  11. data/lib/lyrics/cli/pluginadapter.rb +56 -0
  12. data/lib/lyrics/cli/plugins.rb +79 -0
  13. data/lib/lyrics/cli/wikipluginadapter.rb +139 -0
  14. data/lib/lyrics/i18n/README +1 -0
  15. data/lib/lyrics/i18n/en.rb +181 -0
  16. data/lib/lyrics/i18n/es.rb +181 -0
  17. data/lib/lyrics/i18n/i18n.rb +126 -0
  18. data/lib/lyrics/i18n/sk.rb +174 -0
  19. data/lib/lyrics/itrans/COPYRIGHT +31 -0
  20. data/lib/lyrics/itrans/itrans +0 -0
  21. data/lib/lyrics/itrans/itrans.txt +8 -0
  22. data/lib/lyrics/itrans/lyric.txt +23 -0
  23. data/lib/lyrics/itrans/udvng.ifm +206 -0
  24. data/lib/lyrics/lyrics.rb +567 -0
  25. data/lib/lyrics/lyrics_AZLyrics.rb +113 -0
  26. data/lib/lyrics/lyrics_DarkLyrics.rb +124 -0
  27. data/lib/lyrics/lyrics_Giitaayan.rb +124 -0
  28. data/lib/lyrics/lyrics_Jamendo.rb +166 -0
  29. data/lib/lyrics/lyrics_LeosLyrics.rb +142 -0
  30. data/lib/lyrics/lyrics_LoudSongs.rb +135 -0
  31. data/lib/lyrics/lyrics_LyricWiki.rb +328 -0
  32. data/lib/lyrics/lyrics_LyricsDownload.rb +118 -0
  33. data/lib/lyrics/lyrics_LyricsMania.rb +141 -0
  34. data/lib/lyrics/lyrics_Lyriki.rb +286 -0
  35. data/lib/lyrics/lyrics_SeekLyrics.rb +108 -0
  36. data/lib/lyrics/lyrics_Sing365.rb +103 -0
  37. data/lib/lyrics/lyrics_TerraLetras.rb +126 -0
  38. data/lib/lyrics/mediawikilyrics.rb +1417 -0
  39. data/lib/lyrics/utils/formdata.rb +56 -0
  40. data/lib/lyrics/utils/htmlentities.rb +291 -0
  41. data/lib/lyrics/utils/http.rb +198 -0
  42. data/lib/lyrics/utils/itrans.rb +160 -0
  43. data/lib/lyrics/utils/logger.rb +123 -0
  44. data/lib/lyrics/utils/strings.rb +378 -0
  45. data/lib/lyrics/utils/xmlhash.rb +111 -0
  46. data/lyrics.gemspec +98 -0
  47. data/spec/lyrics_spec.rb +7 -0
  48. data/spec/spec.opts +1 -0
  49. data/spec/spec_helper.rb +9 -0
  50. metadata +137 -0
@@ -0,0 +1,108 @@
1
+ # Copyright (C) 2007 by Sergio Pistone
2
+ # sergio_pistone@yahoo.com.ar
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the
16
+ # Free Software Foundation, Inc.,
17
+ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
+
19
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
20
+
21
+ require "utils/strings"
22
+ require "lyrics"
23
+
24
+ class SeekLyrics < Lyrics
25
+
26
+ @@white_chars = "'\"¿?¡!()[].,;:-/& "
27
+
28
+ def SeekLyrics.site_host()
29
+ return "www.seeklyrics.com"
30
+ end
31
+
32
+ def SeekLyrics.site_name()
33
+ return "Seek Lyrics"
34
+ end
35
+
36
+ def SeekLyrics.lyrics_test_data()
37
+ return [
38
+ Request.new( "Nirvana", "Smells Like Teen Spirit", "Nevermind" ),
39
+ Request.new( "Radiohead", "Optimistic", "Kid A" ),
40
+ Request.new( "Massive Attack", "Protection", "Protection" ),
41
+ Request.new( "Portishead", "Wandering Star", "Dummy" ),
42
+ ]
43
+ end
44
+
45
+ def SeekLyrics.build_song_add_url( request )
46
+ return "http://#{site_host()}/submit.php"
47
+ end
48
+
49
+ def cleanup_token( token )
50
+ token = token.tr( @@white_chars, " " )
51
+ token.strip!()
52
+ token.tr!( " ", "-" )
53
+ return token
54
+ end
55
+
56
+ def build_lyrics_fetch_data( request )
57
+ artist = cleanup_token( request.artist.gsub( /^the /i, "" ) )
58
+ title = cleanup_token( request.title )
59
+ return FetchPageData.new( "http://#{site_host()}/lyrics/#{artist}/#{title}.html", nil, { "Cookie"=>"pass=deleted" } )
60
+ end
61
+
62
+ def parse_lyrics( response, page_body )
63
+
64
+ page_body = Strings.latin12utf8( page_body )
65
+ page_body.tr_s!( " \n\r\t", " " )
66
+ page_body.tr_s!( "’", "'" )
67
+
68
+ if (md = /<b><h2>([^<-]+) - ([^<]+) Lyrics<\/h2><\/b>/i.match( page_body ))
69
+ response.artist, response.title = md[1].strip(), md[2].strip()
70
+ end
71
+
72
+ return if ! page_body.sub!( /^(.*)<a href="http:\/\/www\.ringtonematcher\.com.*$/i, "\\1" )
73
+ return if ! page_body.sub!( /^.*<img src="\/images\/phone-right\.gif" [^<]+><\/a>/i, "" )
74
+
75
+ page_body.gsub!( /\ ?<br ?\/?> ?/i, "\n" )
76
+ page_body.strip!()
77
+
78
+ response.lyrics = page_body
79
+
80
+ end
81
+
82
+ def build_suggestions_fetch_data( request )
83
+ artist = cleanup_token( request.artist.gsub( /^the /i, "" ) )
84
+ return FetchPageData.new( "http://#{site_host()}/lyrics/#{artist}/showall.html", nil, { "Cookie"=>"pass=deleted" } )
85
+ end
86
+
87
+ def parse_suggestions( request, page_body, page_url )
88
+
89
+ page_body = Strings.latin12utf8( page_body )
90
+ page_body.tr_s!( " \n\r\t", " " )
91
+ page_body.tr_s!( "’", "'" )
92
+
93
+ suggestions = []
94
+
95
+ return suggestions if ! page_body.gsub!( /.*<tr><td width="50%"><\/td><td width="50%"><\/td><\/tr>/, "" )
96
+ return suggestions if ! page_body.gsub!( /<\/table>.*$/, "" )
97
+
98
+ page_body.split( /<td>&nbsp;-&nbsp;&nbsp;/i ).each() do |entry|
99
+ if (md = /<a href="([^"]+)"[^>]*>([^<]+)<\/a><\/td>/i.match( entry ))
100
+ suggestions << Suggestion.new( request.artist, md[2], "http://#{site_host()}#{md[1]}" )
101
+ end
102
+ end
103
+
104
+ return suggestions
105
+
106
+ end
107
+
108
+ end
@@ -0,0 +1,103 @@
1
+ # Copyright (C) 2006-2008 by Sergio Pistone
2
+ # sergio_pistone@yahoo.com.ar
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the
16
+ # Free Software Foundation, Inc.,
17
+ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
+
19
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
20
+
21
+ require "lyrics"
22
+
23
+ class Sing365 < Lyrics
24
+
25
+ def Sing365.site_host()
26
+ return "www.sing365.com"
27
+ end
28
+
29
+ def Sing365.site_name()
30
+ return "Sing365"
31
+ end
32
+
33
+ def Sing365.lyrics_test_data()
34
+ return [
35
+ Request.new( "Nirvana", "Smells Like Teen Spirit", "Nevermind" ),
36
+ Request.new( "The Cranberries", "Linger", "Everybody Else Is Doing It, So Why Can't We?" ),
37
+ Request.new( "Pearl Jam", "Porch", "Ten" ),
38
+ Request.new( "The Smashing Pumpkins", "Mayonaise", "Siamese Dream" ),
39
+ ]
40
+ end
41
+
42
+ def Sing365.build_song_add_url( request )
43
+ return build_google_feeling_lucky_url( request.artist )
44
+ end
45
+
46
+ def build_lyrics_fetch_data( request )
47
+ return FetchPageData.new( build_google_feeling_lucky_url( request.artist, request.title ) )
48
+ end
49
+
50
+ def lyrics_page_valid?( request, page_body, page_url )
51
+ md = /<title>([^<]+) - ([^<]+) LYRICS<\/title>/i.match( page_body )
52
+ return md ?
53
+ Strings.normalize( request.artist ) == Strings.normalize( md[1] ) &&
54
+ Strings.normalize( request.title ) == Strings.normalize( md[2] ) :
55
+ false
56
+ end
57
+
58
+ def parse_lyrics( response, page_body )
59
+
60
+ page_body.tr_s!( " \n\r\t", " " )
61
+
62
+ if (md = /<meta name="Description" content="([^"]+) lyrics performed by ([^"]+)">/i.match( page_body ))
63
+ response.artist, response.title = md[1], md[2]
64
+ end
65
+
66
+ return if ! (md = /<img src="?http:\/\/#{site_host()}\/images\/phone2\.gif"? border="?0"?><br><br><\/div>(.*)<div align="?center"?><br><br><img src="?http:\/\/#{site_host()}\/images\/phone\.gif"?/i.match( page_body ))
67
+
68
+ page_body = md[1]
69
+ page_body.gsub!( /\ ?<br ?\/?> ?/i, "\n" )
70
+ page_body.gsub!( /\n{3,}/, "\n\n" )
71
+
72
+ response.lyrics = page_body
73
+
74
+ end
75
+
76
+ def build_suggestions_fetch_data( request )
77
+ return FetchPageData.new( build_google_feeling_lucky_url( request.artist ) )
78
+ end
79
+
80
+ def suggestions_page_valid?( request, page_body, page_url )
81
+ md = /<title>([^<]+) LYRICS<\/title>/i.match( page_body )
82
+ return md ? Strings.normalize( request.artist ) == Strings.normalize( md[1] ) : false
83
+ end
84
+
85
+ def parse_suggestions( request, page_body, page_url )
86
+
87
+ page_body.tr_s!( " \n\r\t", " " )
88
+
89
+ suggestions = []
90
+
91
+ return suggestions if ! (md = /<img src="?http:\/\/#{site_host()}\/images\/phone2\.gif"? border="?0"?><br><br><\/div>(.*)<\/lu><br><div align="?center"?><br><img src="?http:\/\/#{site_host()}\/images\/phone\.gif"?/i.match( page_body ))
92
+
93
+ md[1].split( "<li>" ).each() do |entry|
94
+ if (md = /<a href="([^"]+)"[^>]*>([^<]+) Lyrics<\/a>/i.match( entry ))
95
+ suggestions << Suggestion.new( request.artist, md[2], "http://#{site_host()}#{md[1]}" )
96
+ end
97
+ end
98
+
99
+ return suggestions
100
+
101
+ end
102
+
103
+ end
@@ -0,0 +1,126 @@
1
+ # Copyright (C) 2006-2008 by Sergio Pistone
2
+ # sergio_pistone@yahoo.com.ar
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the
16
+ # Free Software Foundation, Inc.,
17
+ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
+
19
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
20
+
21
+ require "utils/strings"
22
+ require "utils/htmlentities"
23
+ require "lyrics"
24
+
25
+ require "cgi"
26
+
27
+ class TerraLetras < Lyrics
28
+
29
+ def TerraLetras.site_host()
30
+ return "letras.terra.com.br"
31
+ end
32
+
33
+ def TerraLetras.site_name()
34
+ return "Terra Letras"
35
+ end
36
+
37
+ def TerraLetras.known_url?( url )
38
+ return url.index( "http://#{site_host}" ) == 0 || /^http:\/\/.*\.letras\.terra\.com\.br\/letras\/[0-9]+/.match( url )
39
+ end
40
+
41
+ def TerraLetras.lyrics_test_data()
42
+ return [
43
+ Request.new( "The Cranberries", "Linger", "Everybody Else Is Doing It, So Why Can't We?" ),
44
+ Request.new( "Radiohead", "Optimistic", "Kid A" ),
45
+ Request.new( "Mark Lanegan", "One Way Street", "Field Songs" ),
46
+ Request.new( "U2", "One", "Achtung Baby" ),
47
+ ]
48
+ end
49
+
50
+ def TerraLetras.build_song_add_url( request )
51
+ return "http://#{site_host()}/envie_artista.php"
52
+ end
53
+
54
+ def TerraLetras.build_google_feeling_lucky_url( artist, title=nil )
55
+ if title
56
+ query = Strings.google_search_quote( "#{title} letra" )
57
+ query << " "
58
+ query << Strings.google_search_quote( artist )
59
+ else
60
+ query = Strings.google_search_quote( "letras de #{artist}" )
61
+ query << " "
62
+ query << Strings.google_search_quote( "#{artist} letras" )
63
+ end
64
+ return Strings.build_google_feeling_lucky_url( query, site_host() )
65
+ end
66
+
67
+ def build_lyrics_fetch_data( request )
68
+ artist = Strings.utf82latin1( request.artist )
69
+ title = Strings.utf82latin1( request.title )
70
+ return FetchPageData.new( "http://#{site_host()}/winamp.php?musica=#{CGI.escape(title)}&artista=#{CGI.escape(artist)}" )
71
+ end
72
+
73
+ def parse_lyrics( response, page_body )
74
+
75
+ page_body = Strings.latin12utf8( page_body )
76
+ page_body.tr_s!( " \n\r\t", " " )
77
+
78
+ if response.url.index( "http://#{site_host}/winamp.php?" ) == 0 # direct fetch
79
+ if (md = /<h1><a href='[^']+' target='_blank'>([^<]+)<\/a><\/h1>/.match( page_body ))
80
+ response.title = md[1]
81
+ end
82
+ if (md = /<h2><a href='[^']+' target='_blank'>([^<]+)<\/a><\/h2>/.match( page_body ))
83
+ response.artist = md[1]
84
+ end
85
+ return if ! page_body.gsub!( /^.*corrigir letra<\/a><\/p><p>/, "" )
86
+ return if ! page_body.gsub!( /<\/p>.*$/, "" )
87
+ else # fetched from suggestions/url
88
+ if (md = /<h2>([^<]+)<\/h2> <h2 id='sz'>([^<]+)<\/h2>/.match( page_body ))
89
+ response.title, response.artist = md[1], md[2]
90
+ end
91
+ return if ! page_body.sub!( /^.*<p id='cmp'>[^<]*<\/p> <p>/, "" )
92
+ return if ! page_body.sub!( /<\/p>.*/, "" )
93
+ end
94
+
95
+ page_body.gsub!( /\ ?<br ?\/?> ?/i, "\n" )
96
+ page_body.gsub!( /\n{3,}/, "\n\n" )
97
+ response.lyrics = page_body
98
+
99
+ end
100
+
101
+ def build_suggestions_fetch_data( request )
102
+ return FetchPageData.new( build_google_feeling_lucky_url( request.artist ) )
103
+ end
104
+
105
+ def parse_suggestions( request, page_body, page_url )
106
+
107
+ page_body = Strings.latin12utf8( page_body )
108
+ page_body.tr_s!( " \n\r\t", " " )
109
+ HTMLEntities.decode!( page_body )
110
+
111
+ suggestions = []
112
+
113
+ return suggestions if ! page_body.gsub!( /^.*<ul class='top' id='bgn'>/, "" )
114
+ return suggestions if ! page_body.gsub!( /<\/ul>.*$/, "" )
115
+
116
+ page_body.split( /<\/li> ?<li>/ ).each do |entry|
117
+ if (md = /<a href="([^"]+)">([^<]+) - ([^<]+)<\/a>/.match( entry ))
118
+ suggestions << Suggestion.new( md[2], md[3], "http://#{site_host()}#{md[1]}" )
119
+ end
120
+ end
121
+
122
+ return suggestions
123
+
124
+ end
125
+
126
+ end
@@ -0,0 +1,1417 @@
1
+ # Copyright (C) 2006-2008 by Sergio Pistone
2
+ # sergio_pistone@yahoo.com.ar
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the
16
+ # Free Software Foundation, Inc.,
17
+ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
+
19
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
20
+ require "utils/http"
21
+ require "utils/xmlhash"
22
+ require "utils/formdata"
23
+ require "utils/strings"
24
+ require "utils/htmlentities"
25
+ require "i18n/i18n"
26
+ require "lyrics"
27
+
28
+ require "md5"
29
+ require "cgi"
30
+
31
+ class MediaWikiLyrics < Lyrics
32
+
33
+ class TrackData
34
+
35
+ attr_reader :artist, :title, :year, :length, :genre
36
+
37
+ def initialize( artist, title, length=nil, genre=nil )
38
+ @artist = artist.strip().freeze()
39
+ @title = title.strip().freeze()
40
+ @length = 0
41
+ if length
42
+ tokens = length.to_s().split( ":" ).reverse()
43
+ tokens.size.times() { |idx| @length += (60**idx) * tokens[idx].strip().to_i() }
44
+ end
45
+ @genre = genre ? genre.strip().freeze() : nil
46
+ end
47
+
48
+ end
49
+
50
+ class AlbumData
51
+
52
+ attr_reader :tracks, :artist, :title, :year, :month, :day, :length, :genres
53
+
54
+ # NOTE: the artist is obtained from the tracks data. If it's not
55
+ # the same for all tracks it would be varios_artists_name.
56
+ def initialize( tracks, title, year, month=0, day=0, various_artists_name="(Various Artists)" )
57
+
58
+ raise "tracks, title and year values can't be nil" if ! title || ! year || ! tracks
59
+ raise "tracks value can't be empty" if tracks.empty?
60
+
61
+ @tracks = tracks.clone().freeze()
62
+ @title = title.strip().freeze()
63
+ @year = year.kind_of?( String ) ? year.strip().to_i() : year.to_i()
64
+ @month = month.kind_of?( String ) ? month.strip().to_i() : month.to_i()
65
+ @day = day.kind_of?( String ) ? day.strip().to_i() : day.to_i()
66
+ @various_artists_name = various_artists_name.strip().freeze()
67
+
68
+ @artist = nil
69
+ @genres = []
70
+ genre = nil
71
+ normalized_artist = nil
72
+ @length = 0
73
+ @tracks.each() do |track|
74
+ if @artist == nil
75
+ @artist = track.artist
76
+ normalized_artist = Strings.normalize( @artist )
77
+ elsif normalized_artist != Strings.normalize( track.artist )
78
+ @artist = various_artists_name
79
+ end
80
+ if track.genre && ! @genres.include?( genre = String.capitalize( track.genre, true ) )
81
+ @genres.insert( -1, genre )
82
+ end
83
+ @length += track.length
84
+ end
85
+ @genres.freeze()
86
+
87
+ end
88
+
89
+ def various_artists?()
90
+ return @artist == @various_artists_name
91
+ end
92
+
93
+ end
94
+
95
+ class SongData
96
+
97
+ attr_reader :artist, :title, :lyrics, :album, :year, :credits, :lyricists
98
+
99
+ def initialize( artist, title, lyrics, album=nil, year=nil, credits=nil, lyricists=nil )
100
+
101
+ raise "artist and title values can't be nil" if ! artist || ! title
102
+
103
+ @artist = artist.strip().freeze()
104
+ @title = title.strip().freeze()
105
+ @lyrics = lyrics ? lyrics.strip().freeze() : nil
106
+ @album = album ? album.strip().freeze() : nil
107
+ @year = year.kind_of?( String ) ? year.strip().to_i() : year.to_i()
108
+
109
+ credits = credits.split( ";" ) if credits.is_a?( String )
110
+ @credits = []
111
+ credits.each() { |value| @credits << value.strip().freeze() } if credits.is_a?( Array )
112
+ @credits.freeze()
113
+
114
+ lyricists = lyricists.split( ";" ) if lyricists.is_a?( String )
115
+ @lyricists = []
116
+ lyricists.each() { |value| @lyricists << value.strip().freeze() } if lyricists.is_a?( Array )
117
+ @lyricists.freeze()
118
+
119
+ end
120
+
121
+ def instrumental?()
122
+ return @lyrics == nil
123
+ end
124
+
125
+ end
126
+
127
+
128
+ @@NAME = "WL".freeze()
129
+ @@VERSION = "0.13.4".freeze()
130
+ @@AUTH_INTERVAL = 60*60 # check authorization every hour (it's only really checked if the user tries to submit something)
131
+ @@REDIRECT_FOLLOWUPS = 3
132
+
133
+ @@SEARCH_RESULT_TITLE = "title".freeze()
134
+ @@SEARCH_RESULT_URL = "url".freeze()
135
+
136
+ def MediaWikiLyrics.version()
137
+ return @@VERSION
138
+ end
139
+
140
+ def version()
141
+ return self.class.version()
142
+ end
143
+
144
+ attr_reader :username, :password
145
+
146
+ def initialize( cleanup_lyrics=true, logger=nil, username=nil, password=nil )
147
+ super( cleanup_lyrics, logger )
148
+ @logged_in = false
149
+ @cookie = nil
150
+ @last_auth_check = 0 # last time we checked the control page
151
+ @authorized = false
152
+ @username = username ? username.clone().freeze() : nil
153
+ @password = password ? password.clone().freeze() : nil
154
+ end
155
+
156
+ def control_page()
157
+ return self.class.control_page()
158
+ end
159
+
160
+ def logged_in?()
161
+ return @logged_in
162
+ end
163
+
164
+ def build_lyrics_fetch_data( request )
165
+ return FetchPageData.new( build_song_rawdata_url( request.artist, request.title ) )
166
+ end
167
+
168
+ def page_error?( page_body )
169
+
170
+ # database error
171
+ db_error = /<!-- start content -->\s*(<p>|)A database query syntax error has occurred\.\s*This may indicate a bug in the software\.*/m.match( page_body )
172
+
173
+ return true if db_error != nil
174
+
175
+ # page title error
176
+ title_error = /<!-- start content -->\s*(<p>|)The requested page title was invalid, empty, or an incorrectly linked inter-language or inter-wiki title\. It may contain one( or|) more characters which cannot be used in titles\.*/m.match( page_body )
177
+
178
+ return title_error != nil
179
+
180
+ end
181
+ protected :page_error?
182
+
183
+ def MediaWikiLyrics.fetch_content_page( page_url, follow_redirects=@@REDIRECT_FOLLOWUPS )
184
+
185
+ response, page_url = HTTP.fetch_page_get( page_url )
186
+ page_url = page_url.sub( "&action=raw", "" )
187
+
188
+ page_body = response ? response.body() : nil
189
+ return nil, page_url if page_body == nil
190
+
191
+ # work around redirects pages
192
+ count = 0
193
+ articles = [ parse_url( page_url ) ]
194
+ while ( count < follow_redirects && (md = /#redirect ?('''|)\[\[([^\]]+)\]\]('''|)/i.match( page_body )) )
195
+
196
+ article = cleanup_article( md[2] )
197
+ if articles.include?( article ) # circular redirect found
198
+ return nil, url
199
+ else
200
+ articles << article
201
+ end
202
+
203
+ response, page_url = HTTP.fetch_page_get( build_rawdata_url( article ) )
204
+ page_url = page_url.sub( "&action=raw", "" )
205
+
206
+ page_body = response ? response.body() : nil
207
+ return nil, page_url if page_body == nil
208
+
209
+ count += 1
210
+ end
211
+
212
+ page_body = "" if page_body.strip() == "/* Empty */"
213
+
214
+ return page_body, page_url
215
+
216
+ end
217
+
218
+ def fetch_content_page( url, follow_redirects=@@REDIRECT_FOLLOWUPS )
219
+ self.class.fetch_content_page( url, follow_redirects )
220
+ end
221
+
222
+ def fetch_lyrics_page( fetch_data )
223
+ return nil, nil if fetch_data.url == nil
224
+ log( "Fetching lyrics page... ", 0 )
225
+ page_body, page_url = fetch_content_page( fetch_data.url )
226
+ if page_body
227
+ log( "OK" )
228
+ log( response.body(), 2 ) if verbose_log?
229
+ else
230
+ log( "ERROR" )
231
+ end
232
+ return page_body, page_url
233
+ end
234
+
235
+ def lyrics_page_valid?( request, page_body, page_url )
236
+ return ! page_error?( page_body )
237
+ end
238
+
239
+ def build_suggestions_fetch_data( request )
240
+ return FetchPageData.new( build_song_search_url( request.artist, request.title ) )
241
+ end
242
+
243
+ def lyrics_from_url( request, url )
244
+ # we fetch and parse lyrics from raw content mode so we need the right url format
245
+ if ! url.index( "&action=raw" )
246
+ artist, title = parse_song_url( url )
247
+ url = build_song_rawdata_url( artist, title, false )
248
+ end
249
+ return super( request, url )
250
+ end
251
+
252
+ def parse_suggestions( request, page_body, page_url )
253
+ suggestions = []
254
+ parse_search_results( page_body, true ).each() do |result|
255
+ artist, title = parse_song_url( result[@@SEARCH_RESULT_URL] )
256
+ if ! Strings.empty?( artist ) && ! Strings.empty?( title ) && /\ \([0-9]{4,4}\)$/.match( title ) == nil
257
+ suggestions << Suggestion.new( artist, title, result[@@SEARCH_RESULT_URL] )
258
+ end
259
+ end
260
+ return suggestions
261
+ end
262
+
263
+ def login( username=@username, password=@password, force=false )
264
+
265
+ return true if ! force && @logged_in && username == @username && password == @password
266
+
267
+ @username, @password = username, password
268
+ notify = ! @logged_in || ! force
269
+
270
+ if ! @username || ! @password
271
+ notify( I18n.get( "wiki.login.error", @username ? @username : "<NONE>" ) ) if notify
272
+ return @logged_in = false
273
+ end
274
+
275
+ notify( I18n.get( "wiki.login.attempt", @username ) ) if notify
276
+
277
+ headers = { "Keep-Alive"=>"300", "Connection"=>"keep-alive" }
278
+ resp, url = HTTP.fetch_page_get( "http://#{site_host()}/index.php?title=Special:Userlogin", headers )
279
+ @cookie = resp.response["set-cookie"].split( "; " )[0]
280
+
281
+ params = { "wpName"=>@username, "wpPassword"=>@password, "wpLoginattempt"=>"Log In" }
282
+ headers.update( { "Cookie"=>@cookie } )
283
+ resp, url = HTTP.fetch_page_post( "http://#{site_host()}/index.php?title=Special:Userlogin&action=submitlogin", params, headers, -1 )
284
+
285
+ data = resp.body()
286
+ data.gsub!( /[ \t\n]+/, " " )
287
+
288
+ @logged_in = (/<h2>Login error:<\/h2>/.match( data ) == nil)
289
+ @logged_in = (/<h1 class="firstHeading">Login successful<\/h1>/.match( data ) != nil) if @logged_in # recheck
290
+
291
+ notify( I18n.get( "wiki.login." + (@logged_in ? "success" : "error"), @username ) ) if notify
292
+
293
+ return @logged_in
294
+
295
+ end
296
+
297
+ def logout()
298
+ @cookie = false
299
+ if @logged_in
300
+ @logged_in = false
301
+ notify( I18n.get( "wiki.logout", @username ) )
302
+ end
303
+ end
304
+
305
+ def authorized?()
306
+
307
+ # debug( "AUTHORIZED INITIAL VALUE: #{@authorized}" )
308
+
309
+ return @authorized if (Time.new().to_i() - @last_auth_check) < @@AUTH_INTERVAL
310
+
311
+ body, url = fetch_content_page( build_rawdata_url( control_page() ) )
312
+ return false if ! body
313
+
314
+ # debug( "CONTROL PAGE:\n#{body}" )
315
+
316
+ @last_auth_check = Time.new().to_i()
317
+
318
+ control_data = {}
319
+ body.split( "\n" ).each() do |line|
320
+ if (md = /^([^=]+)=(.*)$/.match( line ))
321
+ control_data[md[1].strip()] = md[2].strip()
322
+ end
323
+ end
324
+
325
+ if control_data["BlockedVersions"].to_s().strip().split( /\s*;\s*/ ).include?( version() )
326
+ notify( I18n.get( "wiki.control.versionblocked", version() ) )
327
+ @authorized = false
328
+ # debug( "VERSION BLOCKED" )
329
+ elsif control_data["BlockedUsers"].to_s().strip().downcase().split( /\s*;\s*/ ).include?( @username.to_s().downcase() )
330
+ notify( I18n.get( "wiki.control.userblocked", @username ) )
331
+ @authorized = false
332
+ # debug( "USER BLOCKED" )
333
+ else
334
+
335
+ if (last_version = control_data["LastVersion"].to_s().strip().split( "." )).size > 0
336
+ curr_version = self.version().split( "." )
337
+ curr_version.size.times() do |idx|
338
+ if curr_version[idx].to_i() > last_version[idx].to_i()
339
+ break
340
+ elsif curr_version[idx].to_i() < last_version[idx].to_i()
341
+ notify( I18n.get( "wiki.control.updated", control_data["LastVersion"] ) )
342
+ break
343
+ end
344
+ end
345
+ end
346
+
347
+ @authorized = true
348
+ # debug( "SUBMIT ALLOWED" )
349
+ end
350
+
351
+ return @authorized
352
+ end
353
+
354
+ def restore_session_params( username, password, cookie, last_auth_check=0, authorized=false )
355
+ if ! Strings.empty?( cookie ) && ! Strings.empty?( username )
356
+ @username = username
357
+ @password = password
358
+ @cookie = cookie
359
+ @last_auth_check = last_auth_check.to_i()
360
+ @authorized = authorized.to_s() == 'true'
361
+ @logged_in = true
362
+ notify( I18n.get( "wiki.session.restore.success", @username ) )
363
+ return true
364
+ else
365
+ notify( I18n.get( "wiki.session.restore.error", @username ) )
366
+ return false
367
+ end
368
+
369
+ end
370
+
371
+ def restore_session( session_file )
372
+ values = { "username" => nil, "password" => nil, "cookie" => nil, "last_auth_check" => nil, "authorized" => nil }
373
+ if XMLHash.read( session_file, values )
374
+ return restore_session_params(
375
+ Strings.descramble( values["username"] ),
376
+ Strings.descramble( values["password"] ),
377
+ values["cookie"],
378
+ values["last_auth_check"],
379
+ values["authorized"]
380
+ )
381
+ else
382
+ notify( I18n.get( "wiki.session.restore.notfound" ) )
383
+ return false
384
+ end
385
+ end
386
+
387
+ def get_session_params()
388
+ return (@username ? @username.clone() : nil), (@password ? @password.clone() : nil), (@cookie ? @cookie.clone() : nil), @last_auth_check, @authorized
389
+ end
390
+
391
+ def save_session( session_file )
392
+ if ! @logged_in
393
+ notify( I18n.get( "wiki.session.save.error.notloggedin" ) )
394
+ return false
395
+ end
396
+ values = {
397
+ "username" => Strings.scramble( @username ),
398
+ "password" => Strings.scramble( @password ),
399
+ "cookie" => @cookie,
400
+ "last_auth_check" => @last_auth_check,
401
+ "authorized" => @authorized
402
+ }
403
+ if XMLHash.write( session_file, values )
404
+ notify( I18n.get( "wiki.session.save.success", @username ) )
405
+ return true
406
+ else
407
+ notify( I18n.get( "wiki.session.save.error", @username ) )
408
+ return false
409
+ end
410
+ end
411
+
412
+
413
+ def fetch_page_edit_params( page_url )
414
+ headers = { "Keep-Alive"=>"300", "Connection"=>"keep-alive", "Referer"=>"#{page_url}", "Cookie"=>@cookie }
415
+ response, page_url = HTTP.fetch_page_get( "#{page_url}&action=edit", headers )
416
+
417
+ edit_params = {}
418
+ return edit_params if ! response || response.code != "200"
419
+
420
+ page_body = response.body()
421
+ page_body.tr_s!( " \n\r\t", " " )
422
+
423
+ if (md = /<input type=['"]hidden['"] value=['"]([a-fA-F0-9+\\]*)['"] name=['"]wpEditToken['"] ?\/>/.match( page_body ))
424
+ edit_params["edit_token"] = md[1]
425
+ if (md = /<input type=['"]hidden['"] value=['"]([0-9]+)['"] name=['"]wpEdittime['"] ?\/>/.match( page_body ))
426
+ edit_params["edit_time"] = md[1]
427
+ end
428
+ if (md = /<input type=['"]hidden['"] value=['"]([0-9]+)['"] name=['"]wpStarttime['"] ?\/>/.match( page_body ))
429
+ edit_params["start_time"] = md[1]
430
+ end
431
+ edit_params["logged_in"] = (/<li id="pt-logout">/.match( page_body ) != nil)
432
+ end
433
+
434
+ return edit_params
435
+ end
436
+ protected :fetch_page_edit_params
437
+
438
+ def submit_page( page_url, page_content, summary="", watch=true, retries=1, auto_login=true )
439
+
440
+ return false if (! logged_in? && ! auto_login) || ! authorized?
441
+
442
+ (retries+1).times do
443
+
444
+ begin
445
+
446
+ next if ! logged_in? && ! login()
447
+
448
+ # make sure we have the long url format
449
+ page_url = build_url( parse_url( page_url ) )
450
+
451
+ # Try to get the edit token for url, can't continue without it:
452
+ edit_params = fetch_page_edit_params( page_url )
453
+
454
+ if ! edit_params["logged_in"] # site says user is not logged in (session has expired), force relogin
455
+ next if ! login( @username, @password, true )
456
+ # after being successfully logged in, refetch the edit page
457
+ edit_params = fetch_page_edit_params( page_url )
458
+ next if ! edit_params["logged_in"]
459
+ end
460
+
461
+ next if ! edit_params["edit_token"]
462
+
463
+ params = [
464
+ MultipartFormData.text_param( "wpTextbox1", page_content ),
465
+ MultipartFormData.text_param( "wpSummary", summary ),
466
+ MultipartFormData.text_param( "wpWatchthis", watch ? "on" : "off" ),
467
+ MultipartFormData.text_param( "wpSave", "Save page" ),
468
+ MultipartFormData.text_param( "wpSection", "" ),
469
+ MultipartFormData.text_param( "wpStarttime", edit_params["start_time"].to_s() ), # the new revision time
470
+ MultipartFormData.text_param( "wpEdittime", edit_params["edit_time"].to_s() ), # the previous revision time
471
+ MultipartFormData.text_param( "wpEditToken", edit_params["edit_token"] ),
472
+ ]
473
+
474
+ headers = {
475
+ "Keep-Alive" => "300",
476
+ "Connection" => "keep-alive",
477
+ "Referer" => "http://#{site_host()}#{page_url}&action=edit",
478
+ "Cookie" => @cookie,
479
+ }
480
+
481
+ response, page_url = HTTP.fetch_page_post_form_multipart( "#{page_url}&action=submit", params, headers, -1 )
482
+
483
+ return true if response.code == "302" # we should have received a redirect code
484
+
485
+ rescue TimeoutError
486
+ end
487
+ end
488
+
489
+ return false
490
+ end
491
+
492
+ def submit_redirect_page( page_url, link, summary=nil )
493
+ if submit_page( page_url, "#REDIRECT #{link}", summary )
494
+ notify( I18n.get( "wiki.submitredirect.success", link ) )
495
+ return url
496
+ else
497
+ notify( I18n.get( "wiki.submitredirect.error", link ) )
498
+ return nil
499
+ end
500
+ end
501
+
502
+ def upload_file( src_file, dst_file, mime_type, description="", watch=true, auto_login=true )
503
+
504
+ if ! logged_in?
505
+ return false if ! auto_login || ! login()
506
+ end
507
+
508
+ begin
509
+ data = File.new( src_file ).read()
510
+ rescue Exception
511
+ return false
512
+ end
513
+
514
+ params = [
515
+ MultipartFormData.file_param( "wpUploadFile", File.basename( src_file ), mime_type, data ),
516
+ MultipartFormData.text_param( "wpDestFile", dst_file ),
517
+ MultipartFormData.text_param( "wpUploadDescription", description ),
518
+ MultipartFormData.text_param( "wpWatchthis", watch ? "true" : "false" ),
519
+ MultipartFormData.text_param( "wpUpload", "Upload file" ),
520
+ ]
521
+
522
+ headers = {
523
+ "Keep-Alive" => "300",
524
+ "Connection" => "keep-alive",
525
+ "Referer" => "http://#{site_host()}/index.php?title=Special:Upload&wpDestFile=#{CGI.escape(dst_file)}",
526
+ "Cookie" => @cookie,
527
+ }
528
+
529
+ begin
530
+
531
+ response, page_url = HTTP.fetch_page_post_form_multipart(
532
+ "http://#{site_host()}/index.php?title=Special:Upload",
533
+ params,
534
+ headers,
535
+ -1
536
+ )
537
+
538
+ # we have to receive a redirect code
539
+ return true if response.code == "302"
540
+
541
+ # error, possibly an expired session: relogin and try again
542
+ login( @username, @password, true )
543
+ response, page_url = HTTP.fetch_page_post_form_multipart(
544
+ "http://#{site_host()}/index.php?title=Special:Upload",
545
+ params,
546
+ headers,
547
+ -1
548
+ )
549
+
550
+ # again, we should have received a redirect
551
+ return response.code == "302"
552
+
553
+ rescue TimeoutError
554
+ return false
555
+ end
556
+
557
+ end
558
+
559
+ def upload_cover_image( image_path, artist, album, year, watch=true )
560
+
561
+ album_art_name = build_album_art_name( artist, album, year )
562
+ album_art_desc = build_album_art_description( artist, album, year )
563
+ image_path, mime_type = prepare_image_file( image_path )
564
+
565
+ if Strings.empty?( image_path ) || Strings.empty?( mime_type )
566
+ notify( I18n.get( "wiki.uploadcover.error.convert" ) )
567
+ else
568
+ notify( I18n.get( "wiki.uploadcover.uploading", album, artist ) )
569
+ if upload_file( image_path, album_art_name, mime_type, album_art_desc, watch )
570
+ notify( I18n.get( "wiki.uploadcover.success", album, artist ) )
571
+ else
572
+ notify( I18n.get( "wiki.uploadcover.error", album, artist ) )
573
+ end
574
+ end
575
+
576
+ end
577
+
578
+
579
+ def build_tracks( album_data )
580
+ return self.class.build_tracks( album_data )
581
+ end
582
+
583
+ def build_album_page( reviewed, artist, album, year, month, day, tracks, album_art )
584
+ return self.class.build_album_page( reviewed, artist, album, year, month, day, tracks, album_art )
585
+ end
586
+
587
+ def submittable_album_params?( artist, album, year )
588
+ if Strings.empty?( artist )
589
+ notify( I18n.get( "wiki.submitalbum.error.invalidartist" ) )
590
+ return false
591
+ elsif Strings.empty?( album )
592
+ notify( I18n.get( "wiki.submitalbum.error.invalidalbum" ) )
593
+ return false
594
+ elsif year <= 1900
595
+ notify( I18n.get( "wiki.submitalbum.error.invalidyear" ) )
596
+ return false
597
+ else
598
+ return true
599
+ end
600
+ end
601
+ protected :submittable_album_params?
602
+
603
+ # use skip_initial_page_search when you know the page doesn't exists (i.e. when you have already searched it)
604
+ def submit_album_page( album_data, image_path=nil, allow_page_overwrite=true, skip_initial_page_search=false, show_review_dialog=true, must_review=false )
605
+
606
+ show_review_dialog = true if must_review
607
+
608
+ if ! allow_page_overwrite && ! skip_initial_page_search
609
+ notify( I18n.get( "wiki.submitalbum.searchingpage", album_data.title, album_data.artist ) )
610
+ if find_album_page_url( album_data.artist, album_data.title, album_data.year )
611
+ notify( I18n.get( "wiki.submitalbum.pagefound", album_data.title, album_data.artist ) )
612
+ return nil, nil
613
+ else
614
+ notify( I18n.get( "wiki.submitalbum.nopagefound", album_data.title, album_data.artist ) )
615
+ end
616
+ end
617
+
618
+ page_data = {
619
+ "site_name" => site_name(),
620
+ "artist" => album_data.artist,
621
+ "year" => album_data.year,
622
+ "month" => album_data.month,
623
+ "day" => album_data.day,
624
+ "album" => cleanup_title_token( album_data.title ),
625
+ "tracks" => build_tracks( album_data ),
626
+ "reviewed" => false
627
+ }
628
+
629
+ page_data["album_art_name"] = find_album_art_name( page_data["artist"], page_data["album"], page_data["year"] )
630
+ if page_data["album_art_name"] == nil # album art not found, we'll attempt to upload it
631
+ page_data["image_path"] = image_path.to_s()
632
+ attempt_upload = true
633
+ else
634
+ attempt_upload = false
635
+ end
636
+
637
+ # check if the album parameters are "submitteable" content
638
+ return nil, nil if ! submittable_album_params?( page_data["artist"], page_data["album"], page_data["year"] )
639
+
640
+ page_url = build_album_url( page_data["artist"], page_data["album"], page_data["year"], false )
641
+
642
+ page_content = build_album_page(
643
+ page_data["reviewed"],
644
+ page_data["artist"],
645
+ page_data["album"],
646
+ page_data["year"],
647
+ page_data["month"],
648
+ page_data["day"],
649
+ page_data["tracks"],
650
+ page_data["album_art_name"] ?
651
+ page_data["album_art_name"] :
652
+ build_album_art_name( page_data["artist"], page_data["album"], page_data["year"] )
653
+ )
654
+
655
+ if attempt_upload && ! Strings.empty?( page_data["image_path"] )
656
+ upload_cover_image( page_data["image_path"], page_data["artist"], page_data["album"], page_data["year"] )
657
+ end
658
+
659
+ summary = "#{page_data["reviewed"] ? "" : "autogen. "}album page (#{@@NAME}v#{@@VERSION})"
660
+ if submit_page( page_url, page_content, summary )
661
+ notify( I18n.get( "wiki.submitalbum.success", page_data["album"], page_data["artist"] ) )
662
+ return page_url, page_data
663
+ else
664
+ notify( I18n.get( "wiki.submitalbum.error", page_url ) )
665
+ return nil, page_data
666
+ end
667
+
668
+ end
669
+
670
+ def build_song_page( reviewed, artist, title, album, year, credits, lyricist, lyrics )
671
+ self.class.build_song_page( reviewed, artist, title, album, year, credits, lyricist, lyrics )
672
+ end
673
+
674
+
675
+ def submittable_song_params?( artist, song, lyrics, instrumental )
676
+ if Strings.empty?( artist )
677
+ notify( I18n.get( "wiki.submitsong.error.invalidartist" ) )
678
+ return false
679
+ elsif Strings.empty?( song )
680
+ notify( I18n.get( "wiki.submitsong.error.invalidsong" ) )
681
+ return false
682
+ elsif Strings.empty?( lyrics ) && ! instrumental
683
+ notify( I18n.get( "wiki.submitsong.error.nolyrics" ) )
684
+ return false
685
+ else
686
+ return true
687
+ end
688
+ end
689
+ protected :submittable_song_params?
690
+
691
+ # if edit_url is not nil, it's assumed that we're trying to overwrite (edit) a page, otherwise
692
+ # it's assummed that we're trying to create a new page and so overwritting won't be allowed
693
+ # use skip_initial_page_search when you know the page doesn't exists (i.e. when you have already searched it)
694
+ def submit_song_page( song_data, edit_url=nil, skip_initial_page_search=false, show_review_dialog=true, must_review=false )
695
+
696
+ show_review_dialog = true if must_review
697
+
698
+ if ! edit_url && ! skip_initial_page_search
699
+ notify( I18n.get( "wiki.submitsong.searchingpage", song_data.title, song_data.artist ) )
700
+ if find_song_page_url( song_data.artist, song_data.title )
701
+ notify( I18n.get( "wiki.submitsong.pagefound", song_data.title, song_data.artist ) )
702
+ return nil, nil
703
+ else
704
+ notify( I18n.get( "wiki.submitsong.nopagefound", song_data.title, song_data.artist ) )
705
+ end
706
+ end
707
+
708
+ page_data = {
709
+ "site_name" => site_name(),
710
+ "edit_mode" => edit_url != nil,
711
+ "artist" => cleanup_title_token( song_data.artist ),
712
+ "year" => song_data.year,
713
+ "album" => cleanup_title_token( song_data.album.to_s() ),
714
+ "title" => cleanup_title_token( song_data.title ),
715
+ "lyrics" => Strings.cleanup_lyrics( song_data.lyrics.to_s() ),
716
+ "instrumental" => song_data.instrumental?,
717
+ "credits" => song_data.credits.join( "; " ),
718
+ "lyricist" => song_data.lyricists.join( "; " ),
719
+ "reviewed" => false
720
+ }
721
+
722
+ # check if the song parameters are "submitteable" content
723
+ return nil, nil if ! submittable_song_params?( page_data["artist"], page_data["title"], page_data["lyrics"], page_data["instrumental"] )
724
+
725
+ page_url = edit_url ? edit_url : build_song_url( page_data["artist"], page_data["title"], false )
726
+
727
+ page_content = build_song_page(
728
+ page_data["reviewed"],
729
+ page_data["artist"],
730
+ page_data["title"],
731
+ page_data["album"],
732
+ page_data["year"],
733
+ page_data["credits"],
734
+ page_data["lyricist"],
735
+ page_data["lyrics"]
736
+ )
737
+
738
+ summary = "#{page_data["reviewed"] ? "" : "autogen. "}song page (#{@@NAME}v#{@@VERSION})"
739
+ if submit_page( page_url, page_content, summary )
740
+ notify( I18n.get( "wiki.submitsong.success", page_data["title"], page_data["artist"] ) )
741
+ return page_url, page_data
742
+ else
743
+ notify( I18n.get( "wiki.submitsong.error", page_url ) )
744
+ return nil, page_data
745
+ end
746
+
747
+ end
748
+
749
+ def MediaWikiLyrics.parse_search_results( page_body, content_matches=false )
750
+
751
+ results = []
752
+ return results if page_body == nil
753
+
754
+ page_body.tr_s!( " \n\r\t", " " )
755
+ page_body.gsub!( /\ ?<\/?span[^>]*> ?/, "" )
756
+
757
+ return results if ! page_body.sub!( /^.*<a name="(Article|Page)_title_matches">/, "" ) &&
758
+ ! page_body.sub!( /^.*<a name="No_(article|page)_title_matches">/, "" )
759
+
760
+ page_body.sub!( /<a name="No_(article|page)_text_matches">.*$/, "" ) if ! content_matches
761
+
762
+ return results if ! page_body.gsub!( /<form id="powersearch" method="get" action="[^"]+">.*$/, "" )
763
+ page_body.gsub!( /<\/[uo]l> ?<p( [^>]*|)>View \(previous .*$/, "" )
764
+
765
+ page_body.split( "<li>" ).each() do |entry|
766
+ if (md = /<a href="([^"]*index\.php\/|[^"]*index\.php\?title=|\/)([^"]*)" title="([^"]+)"/.match( entry ))
767
+ result = {
768
+ @@SEARCH_RESULT_URL => "http://#{site_host()}/index.php?title=#{md[2]}",
769
+ @@SEARCH_RESULT_TITLE => md[3]
770
+ }
771
+ results << result if ! content_matches || ! results.include?( result )
772
+ end
773
+ end
774
+
775
+ return results
776
+ end
777
+
778
+ def parse_search_results( page_body, content_matches=false )
779
+ self.class.parse_search_results( page_body, content_matches )
780
+ end
781
+
782
+ @@FIND_TEMPLATE_START = 0
783
+ @@FIND_TEMPLATE_NAME = 1
784
+ @@FIND_TEMPLATE_PARAM = 2
785
+ @@FIND_TEMPLATE_PARAM_VALUE = 3
786
+
787
+ # NOTE: nested templates are only supported in named parameters values
788
+ def MediaWikiLyrics.parse_template_rec( template_text, first_index )
789
+
790
+ template_data = { "name" => "", "params" => {} }
791
+
792
+ operation = @@FIND_TEMPLATE_START
793
+
794
+ param_number = 1
795
+ param_name = nil
796
+ aux_index = -1
797
+
798
+ link = false
799
+
800
+ chr_index = first_index - 1
801
+ chrs_array = template_text.is_a?( Array ) ? template_text : template_text.unpack( "U*" )
802
+ max_chr_index = chrs_array.size-2
803
+
804
+ while ( chr_index <= max_chr_index )
805
+
806
+ chr_index += 1
807
+ chr = [chrs_array[chr_index]].pack( "U" )
808
+
809
+ if operation == @@FIND_TEMPLATE_START
810
+ if chr == "{"
811
+ aux_index = chr_index + 2 # start of template name
812
+ operation = @@FIND_TEMPLATE_NAME
813
+ end
814
+ elsif operation == @@FIND_TEMPLATE_NAME
815
+ if chr == "|"
816
+ template_data["name"] = chrs_array[aux_index..chr_index-1].pack( "U*" ).strip()
817
+ aux_index = chr_index + 1 # start of first parameter (if there is one)
818
+ operation = @@FIND_TEMPLATE_PARAM
819
+ elsif chr == "}" # we may have arrived to the template end (in which case the template has no parameters)
820
+ next_chr = chrs_array[chr_index+1]
821
+ if next_chr && [next_chr].pack( "U" ) == "}" # }} indicates the template end
822
+ template_data["name"] = chrs_array[aux_index..chr_index-1].pack( "U*" ).strip()
823
+ chr_index += 1
824
+ break
825
+ end
826
+ end
827
+ elsif operation == @@FIND_TEMPLATE_PARAM
828
+ if chr == "="
829
+ param_name = chrs_array[aux_index..chr_index-1].pack( "U*" ).strip()
830
+ param_name = param_name.to_i() if param_name.to_i() > 0
831
+ template_data["params"].delete( param_name )
832
+ aux_index = chr_index + 1 # start of parameter value
833
+ operation = @@FIND_TEMPLATE_PARAM_VALUE
834
+ elsif link
835
+ if chr == "]" # we may be at the end of a link (we no longer ignore | and } characters in that case)
836
+ next_chr = chrs_array[chr_index+1]
837
+ if next_chr && [next_chr].pack( "U" ) == "]" # ]] indicates the link end
838
+ link = false
839
+ chr_index += 1
840
+ end
841
+ end
842
+ else
843
+ if chr == "[" # we may be at the start of a link (we ignore the | and } characters in that case)
844
+ next_chr = chrs_array[chr_index+1]
845
+ if next_chr && [next_chr].pack( "U" ) == "[" # [[ indicates the link start
846
+ link = true
847
+ chr_index += 1
848
+ end
849
+ elsif chr == "|" # we arrived to the parameter end (the parameter has no name)
850
+ template_data["params"][param_number] = chrs_array[aux_index..chr_index-1].pack( "U*" )
851
+ HTMLEntities.decode!( template_data["params"][param_number] )
852
+ param_number += 1
853
+ aux_index = chr_index + 1 # start of next parameter (if there is one)
854
+ elsif chr == "}" # we may have arrived to the template end (in which case the parameter has no name)
855
+ next_chr = chrs_array[chr_index+1]
856
+ if next_chr && [next_chr].pack( "U" ) == "}" # }} indicates the template end
857
+ template_data["params"][param_number] = chrs_array[aux_index..chr_index-1].pack( "U*" )
858
+ HTMLEntities.decode!( template_data["params"][param_number] )
859
+ param_number += 1
860
+ chr_index += 1
861
+ break
862
+ end
863
+ end
864
+ end
865
+ elsif operation == @@FIND_TEMPLATE_PARAM_VALUE
866
+ if chr == "{" # we may have arrived at a nested template case (the only one
867
+ # supported: template inserted in a named parameter value)
868
+ next_chr = chrs_array[chr_index+1]
869
+ if next_chr && [next_chr].pack( "U" ) == "{" # {{ indicates the template start
870
+ template_data["params"][param_name] = [] if ! template_data["params"][param_name]
871
+ param_array = template_data["params"][param_name]
872
+ prev_text = chrs_array[aux_index..chr_index-1].pack( "U*" )
873
+ HTMLEntities.decode!( prev_text )
874
+ prev_text.lstrip!() if param_array.empty?
875
+ param_array.insert( -1, prev_text ) if ! prev_text.empty?
876
+ chr_index, nested_template_data = parse_template_rec( chrs_array, chr_index )
877
+ param_array.insert( -1, nested_template_data )
878
+ aux_index = chr_index + 1
879
+ end
880
+ elsif link
881
+ if chr == "]" # we may be at the end of a link (we no longer ignore | and } characters in that case)
882
+ next_chr = chrs_array[chr_index+1]
883
+ if next_chr && [next_chr].pack( "U" ) == "]" # ]] indicates the link end
884
+ link = false
885
+ chr_index += 1
886
+ end
887
+ end
888
+ else
889
+ if chr == "[" # we may be at the start of a link (we ignore the | and } characters in that case)
890
+ next_chr = chrs_array[chr_index+1]
891
+ if next_chr && [next_chr].pack( "U" ) == "[" # [[ indicates the link start
892
+ link = true
893
+ chr_index += 1
894
+ end
895
+ elsif chr == "|" # we arrived to the parameter end
896
+ last_text = chrs_array[aux_index..chr_index-1].pack( "U*" )
897
+ HTMLEntities.decode!( last_text )
898
+ if template_data["params"][param_name]
899
+ param_array = template_data["params"][param_name]
900
+ last_text.rstrip!()
901
+ param_array.insert( -1, last_text ) if ! last_text.empty?
902
+ else
903
+ last_text.strip!()
904
+ template_data["params"][param_name] = last_text
905
+ end
906
+ aux_index = chr_index + 1
907
+ operation = @@FIND_TEMPLATE_PARAM
908
+ elsif chr == "}" # we may have arrived to the template end
909
+ next_chr = chrs_array[chr_index+1]
910
+ if next_chr && [next_chr].pack( "U" ) == "}" # }} indicates the template end
911
+ last_text = chrs_array[aux_index..chr_index-1].pack( "U*" )
912
+ HTMLEntities.decode!( last_text )
913
+ if template_data["params"][param_name]
914
+ param_array = template_data["params"][param_name]
915
+ last_text.rstrip!()
916
+ param_array.insert( -1, last_text ) if ! last_text.empty?
917
+ else
918
+ last_text.strip!()
919
+ template_data["params"][param_name] = last_text
920
+ end
921
+ chr_index += 1
922
+ break
923
+ end
924
+ end
925
+ end
926
+ end
927
+
928
+ end
929
+
930
+ return chr_index, template_data
931
+
932
+ end
933
+ private_class_method :parse_template_rec
934
+
935
+ def MediaWikiLyrics.parse_template( template_text )
936
+ index, template_data = parse_template_rec( template_text, 0 )
937
+ return template_data
938
+ end
939
+
940
+ def parse_template( template )
941
+ return self.class.parse_template( template )
942
+ end
943
+
944
+ def MediaWikiLyrics.prepare_image_file( image_path, size_limit=153600 )
945
+ 4.times() do |trynumb|
946
+ system( "convert", "-quality", (100-trynumb*10).to_s(), image_path, "/tmp/AlbumArt.jpg" )
947
+ return nil, nil if $? != 0
948
+ size = FileTest.size?( "/tmp/AlbumArt.jpg" )
949
+ return "/tmp/AlbumArt.jpg", "image/jpeg" if (size ? size : 0) <= size_limit
950
+ end
951
+ return nil, nil
952
+ end
953
+
954
+ def prepare_image_file( image_path, size_limit=153600 )
955
+ return self.class.prepare_image_file( image_path, size_limit )
956
+ end
957
+
958
+
959
+
960
+ def MediaWikiLyrics.cleanup_title_token( title, downcase=false )
961
+ return cleanup_title_token!( String.new( title ), downcase )
962
+ end
963
+
964
+ def cleanup_title_token( title, downcase=false )
965
+ return self.class.cleanup_title_token( title, downcase )
966
+ end
967
+
968
+ def cleanup_title_token!( title, downcase=false )
969
+ return self.class.cleanup_title_token!( title, downcase )
970
+ end
971
+
972
+ def MediaWikiLyrics.get_sort_name( title )
973
+ return get_sort_name!( String.new( title ) )
974
+ end
975
+
976
+ def get_sort_name( title )
977
+ return self.class.get_sort_name( title )
978
+ end
979
+
980
+ def MediaWikiLyrics.get_sort_name!( title )
981
+
982
+ title.gsub!( /\[[^\]\[]*\]/, "" )
983
+
984
+ Strings.downcase!( title )
985
+
986
+ title.gsub!( /[·\.,;:"`´¿\?¡!\(\)\[\]{}<>#\$\+\*%\^]/, "" )
987
+ title.gsub!( /[\\\/_-]/, " " )
988
+ title.gsub!( "&", "and" )
989
+ title.squeeze!( " " )
990
+ title.strip!()
991
+
992
+ title.gsub!( /^a /, "" ) # NOTE: only valid for English
993
+ title.gsub!( /^an /, "" )
994
+ title.gsub!( /^the /, "" )
995
+ title.gsub!( /^el /, "" )
996
+ title.gsub!( /^le /, "" )
997
+ title.gsub!( /^la /, "" )
998
+ title.gsub!( /^l'([aeiou])/, "\\1" )
999
+ title.gsub!( /^los /, "" )
1000
+ title.gsub!( /^las /, "" )
1001
+ title.gsub!( /^les /, "" )
1002
+ title.gsub!( "'", "" )
1003
+
1004
+ Strings.remove_vocal_accents!( title )
1005
+ Strings.titlecase!( title, false, false )
1006
+
1007
+ return title
1008
+ end
1009
+
1010
+ def get_sort_name!( title )
1011
+ return self.class.get_sort_name!( title )
1012
+ end
1013
+
1014
+ def MediaWikiLyrics.get_sort_letter( title )
1015
+ title = get_sort_name( title )
1016
+ title = title.strip()
1017
+ if title.index( /^[0-9]/ ) == 0
1018
+ return "0-9"
1019
+ else
1020
+ # return title.slice( 0, 1 )
1021
+ return title.unpack( "U*" ).slice( 0, 1 ).pack( "U*" )
1022
+ end
1023
+ end
1024
+
1025
+ def get_sort_letter( title )
1026
+ return self.class.get_sort_letter( title )
1027
+ end
1028
+
1029
+
1030
+
1031
+ # GENERAL FUNCTIONS
1032
+
1033
+ def MediaWikiLyrics.cleanup_article( article, capitalize=true )
1034
+ article = article.gsub( "_", " " )
1035
+ article.strip!()
1036
+ article.squeeze!( " " )
1037
+ Strings.capitalize!( article, false, true ) if capitalize
1038
+ return article
1039
+ end
1040
+
1041
+ def cleanup_article( article )
1042
+ return self.class.cleanup_article( article )
1043
+ end
1044
+
1045
+ def MediaWikiLyrics.parse_link( link )
1046
+
1047
+ if (md = /^ *\[\[([^\|\]]+)\]\] *$/.match( link ))
1048
+ article = cleanup_article( md[1] )
1049
+ return article, article if ! article.empty?
1050
+ end
1051
+
1052
+ if (md = /^ *\[\[([^\|\]]+)\|([^\]]*)\]\] *$/.match( link ))
1053
+ article = cleanup_article( md[1] )
1054
+ if ! article.empty?
1055
+ display = cleanup_article( md[2], false )
1056
+ return article, display if ! display.empty?
1057
+ end
1058
+ end
1059
+
1060
+ return nil, nil
1061
+ end
1062
+
1063
+ def parse_link( link )
1064
+ return self.class.parse_link( link )
1065
+ end
1066
+
1067
+ def MediaWikiLyrics.build_link( article, display=nil )
1068
+ return display ? "[[#{article}|#{display}]]" : "[[#{article}]]"
1069
+ end
1070
+
1071
+ def build_link( article, display=nil )
1072
+ return self.class.build_link( article, display )
1073
+ end
1074
+
1075
+ def MediaWikiLyrics.parse_url( url )
1076
+ if (md = /(https?:\/\/#{site_host()}\/|)(index.php\?title=|wiki\/|)([^&]+)(&.*|)$/.match( url ))
1077
+ return cleanup_article( CGI.unescape( md[3] ) ) # article title
1078
+ else
1079
+ return nil
1080
+ end
1081
+ end
1082
+
1083
+ def parse_url( url )
1084
+ return self.class.parse_url( url )
1085
+ end
1086
+
1087
+ def MediaWikiLyrics.build_url( article )
1088
+ # return "http://#{site_host()}/index.php?title=#{CGI.escape( article )}"
1089
+ return "http://#{site_host()}/index.php?title=#{CGI.escape( article.gsub( " ", "_" ) )}"
1090
+ end
1091
+
1092
+ def build_url( article )
1093
+ return self.class.build_url( article )
1094
+ end
1095
+
1096
+ def MediaWikiLyrics.build_rawdata_url( article )
1097
+ return build_url( article ) + "&action=raw"
1098
+ end
1099
+
1100
+ def build_rawdata_url( article )
1101
+ return self.class.build_rawdata_url( article )
1102
+ end
1103
+
1104
+
1105
+
1106
+
1107
+ # SONG FUNCTIONS
1108
+
1109
+ def MediaWikiLyrics.parse_song_link( link )
1110
+ article, display = parse_link( link )
1111
+ return nil, nil if article == nil
1112
+ if (md = /^([^:]+):(.+)$/.match( article ))
1113
+ return md[1], md[2]
1114
+ else
1115
+ return nil, nil
1116
+ end
1117
+ end
1118
+
1119
+ def parse_song_link( link )
1120
+ return self.class.parse_song_link( link )
1121
+ end
1122
+
1123
+ def MediaWikiLyrics.build_song_link( artist, title, cleanup=true )
1124
+ if cleanup
1125
+ artist = cleanup_title_token( artist )
1126
+ title = cleanup_title_token( title )
1127
+ end
1128
+ return build_link( "#{artist}:#{title}" )
1129
+ end
1130
+
1131
+ def build_song_link( artist, title, cleanup=true )
1132
+ return self.class.build_song_link( artist, title, cleanup )
1133
+ end
1134
+
1135
+ def MediaWikiLyrics.parse_song_url( url )
1136
+ article = parse_url( url )
1137
+ return nil, nil if article == nil
1138
+ if (md = /^([^:]+):(.+)$/.match( article ))
1139
+ return md[1], md[2] # artist, song title
1140
+ else
1141
+ return nil, nil
1142
+ end
1143
+ end
1144
+
1145
+ def parse_song_url( url )
1146
+ return self.class.parse_song_url( url )
1147
+ end
1148
+
1149
+ def MediaWikiLyrics.build_song_url( artist, title, cleanup=true )
1150
+ if cleanup
1151
+ artist = cleanup_title_token( artist )
1152
+ title = cleanup_title_token( title )
1153
+ end
1154
+ return build_url( "#{artist}:#{title}" )
1155
+ end
1156
+
1157
+ def build_song_url( artist, title, cleanup=true )
1158
+ return self.class.build_song_url( artist, title, cleanup )
1159
+ end
1160
+
1161
+ def MediaWikiLyrics.build_song_rawdata_url( artist, title, cleanup=true )
1162
+ return build_song_url( artist, title, cleanup ) + "&action=raw"
1163
+ end
1164
+
1165
+ def build_song_rawdata_url( artist, title, cleanup=true )
1166
+ return self.class.build_song_rawdata_url( artist, title, cleanup )
1167
+ end
1168
+
1169
+ def MediaWikiLyrics.build_song_edit_url( artist, title, cleanup=true )
1170
+ return build_song_url( artist, title, cleanup ) + "&action=edit"
1171
+ end
1172
+
1173
+ def build_song_edit_url( artist, title, cleanup=true )
1174
+ return self.class.build_song_edit_url( artist, title, cleanup )
1175
+ end
1176
+
1177
+ def MediaWikiLyrics.build_song_add_url( request, cleanup=true )
1178
+ return build_song_url( request.artist, request.title, cleanup ) + "&action=edit"
1179
+ end
1180
+
1181
+ def build_song_add_url( request, cleanup=true )
1182
+ return self.class.build_song_add_url( request, cleanup )
1183
+ end
1184
+
1185
+ def MediaWikiLyrics.build_song_search_url( artist, title )
1186
+ artist = cleanup_title_token( artist )
1187
+ title = cleanup_title_token( title )
1188
+ search_string = CGI.escape( "#{artist}:#{title}" )
1189
+ return "http://#{site_host()}/index.php?redirs=1&search=#{search_string}&fulltext=Search&limit=500"
1190
+ end
1191
+
1192
+ def build_song_search_url( artist, title )
1193
+ return self.class.build_song_search_url( artist, title )
1194
+ end
1195
+
1196
+ def MediaWikiLyrics.find_song_page_url( artist, title )
1197
+
1198
+ url = build_song_rawdata_url( artist, title )
1199
+ body, url = fetch_content_page( url )
1200
+
1201
+ if ! Strings.empty?( body ) # page exists
1202
+ return url
1203
+ else
1204
+ artist = Strings.normalize( cleanup_title_token( artist ) )
1205
+ title = Strings.normalize( cleanup_title_token( title ) )
1206
+ normalized_target = "#{artist}:#{title}"
1207
+
1208
+ response, url = HTTP.fetch_page_get( build_song_search_url( artist, title ) )
1209
+ return nil if response == nil
1210
+ parse_search_results( response.body(), true ).each() do |result|
1211
+ normalized_result = result[@@SEARCH_RESULT_TITLE].split( ":" ).each() do |token|
1212
+ Strings.normalize!( token )
1213
+ end.join( ":" )
1214
+ return result[@@SEARCH_RESULT_URL] if normalized_target == normalized_result
1215
+ end
1216
+
1217
+ return nil
1218
+ end
1219
+
1220
+ end
1221
+
1222
+ def find_song_page_url( artist, title )
1223
+ return self.class.find_song_page_url( artist, title )
1224
+ end
1225
+
1226
+
1227
+
1228
+ # ALBUM FUNCTIONS
1229
+
1230
+ def MediaWikiLyrics.parse_album_link( link )
1231
+ article, display = parse_link( link )
1232
+ return nil, nil, nil if article == nil
1233
+ if (md = /^([^:]+):(.+) \(([0-9]{4,4})\)$/.match( article ))
1234
+ return md[1], md[2], md[3]
1235
+ else
1236
+ return nil, nil, nil
1237
+ end
1238
+ end
1239
+
1240
+ def parse_album_link( link )
1241
+ return self.class.parse_album_link( link )
1242
+ end
1243
+
1244
+ def MediaWikiLyrics.build_album_link( artist, album, year, cleanup=true )
1245
+ if cleanup
1246
+ artist = cleanup_title_token( artist )
1247
+ album = cleanup_title_token( album )
1248
+ end
1249
+ return build_link( "#{artist}:#{album} (#{year})" )
1250
+ end
1251
+
1252
+ def build_album_link( artist, album, year, cleanup=true )
1253
+ return self.class.build_album_link( artist, album, year, cleanup )
1254
+ end
1255
+
1256
+ def MediaWikiLyrics.parse_album_url( url )
1257
+ article = parse_url( url )
1258
+ return nil, nil, nil if article == nil
1259
+ if (md = /^([^:]+):(.+) \(([0-9]{4,4})\)$/.match( article ))
1260
+ return md[1], md[2], md[3]
1261
+ else
1262
+ return nil, nil, nil
1263
+ end
1264
+ end
1265
+
1266
+ def parse_album_url( url )
1267
+ return self.class.parse_album_url( url )
1268
+ end
1269
+
1270
+ def MediaWikiLyrics.build_album_url( artist, album, year, cleanup=true )
1271
+ if cleanup
1272
+ artist = cleanup_title_token( artist )
1273
+ album = cleanup_title_token( album )
1274
+ end
1275
+ return build_url( "#{artist}:#{album} (#{year})" )
1276
+ end
1277
+
1278
+ def build_album_url( artist, album, year, cleanup=true )
1279
+ return self.class.build_album_url( artist, album, year, cleanup )
1280
+ end
1281
+
1282
+ def MediaWikiLyrics.build_album_rawdata_url( artist, album, year, cleanup=true )
1283
+ return build_album_url( artist, album, year, cleanup ) + "&action=raw"
1284
+ end
1285
+
1286
+ def build_album_rawdata_url( artist, album, year, cleanup=true )
1287
+ return self.class.build_album_rawdata_url( artist, album, year, cleanup )
1288
+ end
1289
+
1290
+ def MediaWikiLyrics.build_album_edit_url( artist, album, year, cleanup=true )
1291
+ return build_album_url( artist, album, year, cleanup ) + "&action=edit"
1292
+ end
1293
+
1294
+ def build_album_edit_url( artist, album, year, cleanup=true )
1295
+ return self.class.build_album_edit_url( artist, album, year, cleanup )
1296
+ end
1297
+
1298
+ def MediaWikiLyrics.build_album_search_url( artist, album, year )
1299
+ artist = cleanup_title_token( artist )
1300
+ album = cleanup_title_token( album )
1301
+ search_string = CGI.escape( "#{artist}:#{album} (#{year})" )
1302
+ return "http://#{site_host()}/index.php?redirs=1&search=#{search_string}&fulltext=Search&limit=500"
1303
+ end
1304
+
1305
+ def build_album_search_url( artist, album, year )
1306
+ return self.class.build_album_search_url( artist, album, year )
1307
+ end
1308
+
1309
+ def MediaWikiLyrics.find_album_page_url( artist, album, year )
1310
+
1311
+ url = build_album_rawdata_url( artist, album, year )
1312
+ body, url = fetch_content_page( url )
1313
+
1314
+ if ! Strings.empty?( body ) # page exists
1315
+ return url
1316
+ else
1317
+ artist = Strings.normalize( cleanup_title_token( artist ) )
1318
+ album = Strings.normalize( cleanup_title_token( album ) )
1319
+ normalized_target = "#{artist}:#{album} #{year}" # NOTE: normalize removes the parenthesis
1320
+
1321
+ response, url = HTTP.fetch_page_get( build_album_search_url( artist, album, year ) )
1322
+ return nil if response == nil || response.body() == nil
1323
+ parse_search_results( response.body(), true ).each() do |result|
1324
+ normalized_result = result[@@SEARCH_RESULT_TITLE].split( ":" ).each() do |token|
1325
+ Strings.normalize!( token )
1326
+ end.join( ":" )
1327
+ return result[@@SEARCH_RESULT_URL] if normalized_target == normalized_result
1328
+ end
1329
+ return nil
1330
+ end
1331
+
1332
+ end
1333
+
1334
+ def find_album_page_url( artist, album, year )
1335
+ return self.class.find_album_page_url( artist, album, year )
1336
+ end
1337
+
1338
+
1339
+ # ALBUM ART FUNCTIONS
1340
+
1341
+ def build_album_art_name( artist, album, year, extension="jpg", cleanup=true )
1342
+ return self.class.build_album_art_name( artist, album, year, extension, cleanup )
1343
+ end
1344
+
1345
+ def build_album_art_description( artist, album, year, cleanup=true )
1346
+ return self.class.build_album_art_description( artist, album, year, cleanup )
1347
+ end
1348
+
1349
+ def find_album_art_name( artist, album, year )
1350
+ return self.class.find_album_art_name( artist, album, year )
1351
+ end
1352
+
1353
+ def MediaWikiLyrics.find_album_art_url( artist, album, year )
1354
+ if album_art_name = find_album_art_name( artist, album, year )
1355
+ album_art_name.gsub!( " ", "_" )
1356
+ return "http://#{site_host()}/index.php?title=Image:#{CGI.escape(album_art_name)}"
1357
+ else
1358
+ return nil
1359
+ end
1360
+ end
1361
+
1362
+ def find_album_art_url( artist, album, year )
1363
+ return self.class.find_album_art_url( artist, album, year )
1364
+ end
1365
+
1366
+
1367
+ # DATE FUNCTIONS
1368
+
1369
+ @@months = [
1370
+ "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY",
1371
+ "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER"
1372
+ ]
1373
+
1374
+ def MediaWikiLyrics.parse_date( date )
1375
+
1376
+ day, month, year = nil, nil, nil
1377
+
1378
+ if (md = /^(#{@@months.join( "|" )})( \d\d?|), (\d\d\d\d)$/i.match( date ))
1379
+ month, day, year = @@months.index( Strings.upcase( md[1] ) ) + 1, md[2].strip().to_i(), md[3].to_i()
1380
+ elsif (md = /^(\d\d? |)(#{@@months.join( "|" )}) (\d\d\d\d)$/i.match( date ))
1381
+ day, month, year = md[1].strip().to_i(), @@months.index( Strings.upcase( md[2] ) ) + 1, md[3].to_i()
1382
+ elsif /^(\d\d\d\d)-(\d\d)-(\d\d)$/.match( date )
1383
+ year, month, day = md[1].to_i(), md[2].to_i(), md[3].to_i()
1384
+ elsif (md = /^(\d\d\d\d)$/.match( date ))
1385
+ year = md[1].to_i()
1386
+ end
1387
+
1388
+ year = nil if year == 0
1389
+ month = nil if month == 0
1390
+ day = nil if day == 0
1391
+
1392
+ return year, month, day
1393
+
1394
+ end
1395
+
1396
+ def MediaWikiLyrics.build_date( year, month, day )
1397
+
1398
+ year, month, day = year.to_i(), month.to_i(), day.to_i()
1399
+
1400
+ if month != 0
1401
+ month = @@months[month-1].to_s()
1402
+ month = month.slice( 0..0 ).upcase() + month.slice( 1..-1 ).downcase()
1403
+ end
1404
+
1405
+ if day != 0 && month != 0 && year != 0
1406
+ return "#{month} #{day}, #{year}"
1407
+ elsif month != 0 && year != 0
1408
+ return "#{month}, #{year}"
1409
+ elsif year != 0
1410
+ return year.to_s()
1411
+ else
1412
+ return ""
1413
+ end
1414
+
1415
+ end
1416
+
1417
+ end