commit a73140ee37021e0b0f0038c746ef115b1174ae1a Author: pbsds Date: Sun Jan 26 05:30:04 2014 +0100 Initial commit diff --git a/Hatenatools/NTFT.py b/Hatenatools/NTFT.py new file mode 100644 index 0000000..e7fb1d3 --- /dev/null +++ b/Hatenatools/NTFT.py @@ -0,0 +1,140 @@ +#NTFT.py by pbsds +#Free to use as long as credit is given to pbsds / Peder Bergebakken Sundt +#PIL is required to write images to disk +# +#Credit: +# +# -The guys behind TiledGGD at https://code.google.com/p/tiledggd/ +# This sped up the my work a lot +import sys +try: + import Image + hasPIL = True +except ImportError: + hasPIL = False + + +#helpers +def AscDec(ascii, LittleEndian=False):#Converts a ascii string into a decimal + ret = 0 + l = map(ord, ascii) + if LittleEndian: l.reverse() + for i in l: + ret = (ret<<8) | i + return ret +def DecAsc(dec, length=None, LittleEndian=False):#Converts a decimal into an ascii string of chosen length + out = [] + while dec <> 0: + out.insert(0, dec&0xFF) + dec >>= 8 + #"".join(map(chr, out)) + + if length: + if len(out) > length: + #return "".join(map(chr, out[-length:])) + out = out[-length:] + if len(out) < length: + #return "".join(map(chr, [0]*(length-len(out)) + out)) + out = [0]*(length-len(out)) + out + + if LittleEndian: out.reverse() + return "".join(map(chr, out)) + +#Class NTFT: +# +# todo: write documentation +class NTFT: + def __init__(self): + self.Loaded = False + def ReadFile(self, path, size): + f = open(path, "rb") + ret = self.Read(f.read(), size) + f.close() + return ret + def Read(self, data, (w, h)): + if w*h*2 <> len(data): + print "Invalid size" + return False + + self.Image = [[None for _ in xrange(h)] for _ in xrange(w)] + for y in xrange(h): + for x in xrange(w): + pos = (x + y*w)*2 + #print pos + byte = AscDec(data[pos:pos+2], True) + byte = AscDec(data[pos:pos+2], True) + #ARGB1555 -> RGBA8:#require true + a = (byte >> 15 ) * 0xFF + b = (byte >> 10 & 0x1F) * 0xFF / 0x1F + g = (byte >> 5 & 0x1F) * 0xFF / 0x1F + r = (byte & 0x1F) * 0xFF / 0x1F + # #RGB565 -> RGBA8: + # a = 0xFF + # r = (byte >> 11 & 0x1F) * 0xFF / 0x1F + # g = (byte >> 5 & 0x3F) * 0xFF / 0x3F + # b = (byte & 0x1F) * 0xFF / 0x1F + #print x, y + self.Image[x][y] = (r<<24) | (g<<16) | (b<<8) | a#RGBA8 + + self.Loaded = True + return self + def WriteFile(self, path): + pass + def Pack(self): + pass + +#Function WriteImage: +# +# Writes a 2D list if uint32 RGBA values as a image files. +# Designed to work with PPM().Thumbnail or PPM().GetFrame(n) +# +# This function requires the PIl imaging module +def WriteImage(image, outputPath): + if not hasPIL or not image: return False + + out = [] + for y in xrange(len(image[0])): + for x in xrange(len(image)): + out.append(DecAsc(image[x][y], 4)) + + out = Image.fromstring("RGBA", (len(image), len(image[0])), "".join(out)) + + filetype = outputPath[outputPath.rfind(".")+1:] + out.save(outputPath, filetype) + + return True + +#testing: +# i = NTFT().ReadFile("NTFTtests/kaeru.ntft", (64, 32)) +# WriteImage(i.Image, "NTFTtests/kaeru.png") + +# i = NTFT().ReadFile("NTFTtests/News.ntft", (32, 32)) +# WriteImage(i.Image, "NTFTtests/News.png") + +# i = NTFT().ReadFile("NTFTtests/Special Room.ntft", (32, 32)) +# WriteImage(i.Image, "NTFTtests/Special Room.png") + +if __name__ == "__main__": + print " == NTFT.py ==" + print " == by pbsds ==" + print " == v0.05 ==" + print + + if len(sys.argv) < 4: + print "Usage:" + print " NTFT.py " + print "" + print "The NTFT file contain only the colordata, so it's up to the user to find and" + print "store the resolution of the images." + print "32x32 is the normal size for button icons in UGO files." + sys.exit() + + input = sys.argv[1] + output = sys.argv[2] + width = int(sys.argv[3]) + height = int(sys.argv[4]) + + print "Converting..." + WriteImage(NTFT().ReadFile(input, (width, height)).Image, output) + print "Done!" + \ No newline at end of file diff --git a/Hatenatools/PPM.py b/Hatenatools/PPM.py new file mode 100644 index 0000000..cf414d3 --- /dev/null +++ b/Hatenatools/PPM.py @@ -0,0 +1,776 @@ +#PPM.py by pbsds for python 2.7 +#Free to use. Publicated modifications must give credit to pbsds / Peder Bergebakken Sundt +#PIL is required to write frames and thumbnails to disk +# +#Credits: +# +# -Steven for most of the documentation on DSiBrew and his frame decoding example on his talkpage +# -Remark for help on the 8x8 tiling on the preview image. +# -Austin Burk and Jsafive for supplying .tmb files + +import sys, wave#needs os and time aswell +try: + import Image + hasPIL = True +except ImportError: + hasPIL = False + +#helpers: +def AscDec(ascii, LittleEndian=False):#Converts a ascii string into a decimal + ret = 0 + l = map(ord, ascii) + if LittleEndian: l.reverse() + for i in l: + ret = (ret<<8) | i + return ret +def DecAsc(dec, length=None, LittleEndian=False):#Converts a decimal into an ascii string of chosen length + out = [] + while dec <> 0: + out.insert(0, dec&0xFF) + dec >>= 8 + #"".join(map(chr, out)) + + if length: + if len(out) > length: + #return "".join(map(chr, out[-length:])) + out = out[-length:] + if len(out) < length: + #return "".join(map(chr, [0]*(length-len(out)) + out)) + out = [0]*(length-len(out)) + out + + if LittleEndian: out.reverse() + return "".join(map(chr, out)) +def AddPadding(i,pad = 0x10):#used mainly for zipaligning offsets + if i % pad <> 0: + return i + pad - (i % pad) + return i + +#Class PPM: +# +# With this class you can read a Flipnote PPM file into memory and get its attributes. +# You do so by using either instance.ReadFile() or instance.Read() of your instance of the PPM class. +# When reading the PPM file you can specify what parts you need by passing these parameters: +# - DecodeThumbnail = True: The thumbnail will be predecoded. GetThumbnail() will still work if set to False, only takes a bit longer. Default is False +# - ReadFrames = True: The frames are decoded. Disabling this will speed up the PPM reading a lot. Default is True +# - ReadSound = True: Reads the sound data. Default is False +# +# After reading a PPM file, these values are available: +# +# - instance.FrameCount: The number of frames this flipnote consists of +# - instance.Locked: A boolean telling us wether it's locked or not +# - instance.ThumbnailFrameIndex: Which frame the thumbnail is(starting at 0) +# - instance.OriginalAuthorName: The name of the original author in unicode +# - instance.EditorAuthorName: The name of the (previous?) editing author in unicode +# - instance.Username: The username in unicode +# - instance.OriginalAuthorID: The ID of the original author +# - instance.EditorAuthorID: The ID of the last editor, the last user to save the file +# - instance.PreviousEditAuthorID: The ID of the previous editor +# - instance.OriginalFilename: The original filename +# - instance.CurrentFilename: The current filename +# - instance.Date: The date in seconds since January 1'st 2000 +# - instance.GetThumbnail(): The thumbnail stored in a 2D list of uint32 RGBA values. Will decode it first if not done so already +# - instance.GetFrame(n): Returns the specified frame as a 2D list of uint32 RGBA values. Only works if ReadFrames was true when reading the PPM file +# - instance.Looped: A boolean telling us wether it's looped or not +# - instance.SFXUsage: A list telling us when the different sound effects are used: +# instance.SFXUsage[frame] = [SFX1, SFX2, SFX3] where the SFX's are booleans +# - instance.Framespeed: a value between 1 and 8 telling us the speed +# - instance.BGMFramespeed: The framespeed when the BGM was recorded. used for resampling +# - instance.Frames: A list containing the decoded frames' raw data. Only available if ReadFrames was true when reading the PPM file +# instance.Frames[frame] = [Inverted(boolean), [color #1, color #2], frame] +# color #1 and #2 is a value between 0 and 3, check the palette in instance.GetFrame() to see what they are. +# frame is a 3d list, used like this. frame[layer][x][y] = boolean +# To get a colored image of a frame, use instance.GetFrame() +# - instance.SoundData: A list contaning the raw data of the BGM and the 3 sound effects. Only available if ReadSound was true set to true +class PPM: + def __init__(self): + self.Loaded = [False, False, False]#(Meta, Frames, Sound) + self.Frames = None + self.Thumbnail = None + self.RawThumbnail = None + self.SoundData = None + def ReadFile(self, path, DecodeThumbnail=False, ReadFrames=True, ReadSound=False):#Load: (thumbnail, frames, sound) + f = open(path, "rb") + ret = self.Read(f.read(), DecodeThumbnail, ReadFrames, ReadSound) + f.close() + return ret + def Read(self, data, DecodeThumbnail=False, ReadFrames=True, ReadSound=False):#Load: (thumbnail, frames, sound) + if data[:4] <> "PARA" or len(data) <= 0x6a0: + return False + + + #Read the header: + AudioOffset = AscDec(data[4:8], True) + 0x6a0 + AudioLenght = AscDec(data[8:12], True) + + self.FrameCount = AscDec(data[12:14], True) + 1 + self.Locked = ord(data[0x10]) & 0x01 == 1 + self.ThumbnailFrameIndex = AscDec(data[0x12:0x14], True)#which frame the thumbnnail represents + + self.OriginalAuthorName = data[0x14:0x2A].decode("UTF-16LE").split(u"\0")[0] + self.EditorAuthorName = data[0x2A:0x40].decode("UTF-16LE").split(u"\0")[0] + self.Username = data[0x40:0x56].decode("UTF-16LE").split(u"\0")[0] + + self.OriginalAuthorID = data[0x56:0x5e][::-1].encode("HEX").upper() + self.EditorAuthorID = data[0x5E:0x66][::-1].encode("HEX").upper()#the last user to save the file + + self.OriginalFilenameC = data[0x66:0x78]#compressed + self.CurrentFilenameC = data[0x78:0x8a]#compressed + self.OriginalFilename = "%s_%s_%s.tmb" % (self.OriginalFilenameC[:3].encode("HEX").upper(), self.OriginalFilenameC[3:-2], str(AscDec(self.OriginalFilenameC[-2:], True)).zfill(3)) + self.CurrentFilename = "%s_%s_%s.tmb" % (self.CurrentFilenameC[:3].encode("HEX").upper(), self.CurrentFilenameC[3:-2], str(AscDec(self.CurrentFilenameC[-2:], True)).zfill(3)) + + self.PreviousEditAuthorID = data[0x8a:0x92][::-1].encode("HEX").upper()#don't know what this really is + + #self.PartialFilenameC = data[0x92:0x9a]#compressed + + self.Date = AscDec(data[0x9a:0x9e], True)#in seconds since midnight 1'st january 2000 + + self.RawThumbnail = data[0xa0:0x6a0] + if DecodeThumbnail: + self.GetThumbnail()#self.Thumbnail[x][y] = uint32 RGBA + + self.Loaded[0] = True + + #read the animation sequence header: + self.Looped = ord(data[0x06A6]) >> 1 & 0x01 == 1#Unverified? + AnimationOffset = 0x6a8 + AscDec(data[0x6a0:0x6a4], True) + FrameOffsets = [AnimationOffset + AscDec(data[0x06a8+i*4:0x06a8+i*4+4], True) for i in xrange(self.FrameCount)] + + #Read the audio header: + self.SFXUsage = [(i&0x1<>0, i&0x2<>0, i&0x4<>0) for i in map(ord, data[AudioOffset:AudioOffset+self.FrameCount])]#SFXUsage[frame] = (sfx1, sfx2, sfx2) shere sfxX is either 0 or 1 + SoundSize =(AscDec(data[AddPadding(AudioOffset+self.FrameCount, 4) :AddPadding(AudioOffset+self.FrameCount, 4)+ 4], True),#BG music + AscDec(data[AddPadding(AudioOffset+self.FrameCount, 4)+ 4:AddPadding(AudioOffset+self.FrameCount, 4)+ 8], True),#SFX1 + AscDec(data[AddPadding(AudioOffset+self.FrameCount, 4)+ 8:AddPadding(AudioOffset+self.FrameCount, 4)+12], True),#SFX2 + AscDec(data[AddPadding(AudioOffset+self.FrameCount, 4)+12:AddPadding(AudioOffset+self.FrameCount, 4)+16], True))#SFX3 + self.Framespeed = 8 - ord(data[AddPadding(AudioOffset+self.FrameCount, 4) + 16]) + self.BGMFramespeed = 8 - ord(data[AddPadding(AudioOffset+self.FrameCount, 4) + 17])#framespeed when the bgm was recorded + + #Read the Frames: + if ReadFrames: + self.Frames = []#self.Frames[frame] = [inverted(bool), (color1(0-2), color2(0-2)), frame[layer][x][y] = bool] + for i, offset in enumerate(FrameOffsets): + #Read frame header: + Inverted = ord(data[offset]) & 0x01 == 0 + + #Reads which color that will be used: + Colors = (ord(data[offset]) >> 1 & 0x03, + ord(data[offset]) >> 3 & 0x03) + + Frame = self.ExtractFrame(data, offset, self.Frames[i-1][2] if i else None) + + self.Frames.append([Inverted, Colors, Frame]) + + self.Loaded[1] = True + else: + self.Loaded[1] = False + self.Frames = None + + #Read the Audio: + if ReadSound: + self.SoundData = [] + pos = AddPadding(AudioOffset+self.FrameCount+32, 4) + for i in xrange(4): + self.SoundData.append(data[pos:pos+SoundSize[i]]) + pos += SoundSize[i] + + self.Loaded[2] = True + else: + self.Loaded[2] = False + self.SoundData = None + + #return the results + return self + def WriteFile(self, path):#not implented + pass + #Extracting: + def GetFrame(self, frame):#frame is the index in self.Frames. Only works if ReadFrames was True when reading the PPM file + if not self.Loaded[1]: return None + + Inverted, Colors, Frame = self.Frames[frame] + + #Defines the palette: + Palette = [0xFFFFFFFF,0x000000FF,0xFF0000FF,0x0000FFFF] + if Inverted: + Palette[0] = 0x000000FF + Palette[1] = 0xFFFFFFFF + Color1 = Palette[Colors[0]] + Color2 = Palette[Colors[1]] + + out = [] + for x in xrange(256): + out.append([]) + for y in xrange(192): + if Frame[0][x][y]:#Color 1: + out[-1].append(Color1) + elif Frame[1][x][y]:#Color 2: + out[-1].append(Color2) + else:#background: + out[-1].append(Palette[0]) + + return out + def GetThumbnail(self, force=False): + if (not self.Thumbnail or force) and self.RawThumbnail: + if not self.RawThumbnail: + return False + + out = [[0 for _ in xrange(48)] for _ in xrange(64)] + palette = (0xFEFEFEFF,#0 + 0x4F4F4FFF,#1 + 0xFFFFFFFF,#2 + 0x9F9F9FFF,#3 + 0xFF0000FF,#4 + 0x770000FF,#5 + 0xFF7777FF,#6 + 0x00FF00FF,#7- + 0x0000FFFF,#8 + 0x000077FF,#9 + 0x7777FFFF,#A + 0x00FF00FF,#B- + 0xFF00FFFF,#C + 0x00FF00FF,#D- + 0x00FF00FF,#E- + 0x00FF00FF)#F- + + #8x8 tiling: + for ty in range(6): + for tx in range(8): + for y in range(8): + for x in range(0,8,2): + #two colors stored in each byte: + byte = ord(self.RawThumbnail[(ty*512+tx*64+y*8+x)/2]) + out[x+tx*8 ][y+ty*8] = palette[byte & 0xF] + out[x+tx*8+1][y+ty*8] = palette[byte >> 4] + + self.Thumbnail = out + return self.Thumbnail + #sub functions: + def ExtractFrame(self, data, offset, PrevFrame=None): + #defines line encoding storage: + #Enc1 = [0 for i in range(192)] + #Enc2 = [0 for i in range(192)] + #Defines the frame storage: + #Color1Frame = [[0 for i in range(192)] for i in range(256)] + #Color2Frame = [[0 for i in range(192)] for i in range(256)] + + Encoding = [[], []] + Frame = [[[False for _ in xrange(192)] for _ in xrange(256)] for _ in xrange(2)] + + #Read tags: + NewFrame = ord(data[offset]) & 0x80 <> 0 + Unknown = ord(data[offset]) >> 5 & 0x03 + + offset += 1 + + #WIP - framemove: + FrameMove = [0,0] + if Unknown & 0x2:#doesn't work 100%... + move = AscDec(data[offset:offset+2], True) + if move > 128: + FrameMove[0] = move - 256 + else: + FrameMove[1] = 0-move + offset += 2 + if Unknown: + print "Unknown instance:",Unknown,"at offset ",offset-1 + + + #read the line encoding of the layers: + for layer in xrange(2): + for byte in map(ord, data[offset:offset+48]): + Encoding[layer].append(byte & 0x03) + Encoding[layer].append(byte >> 2 & 0x03) + Encoding[layer].append(byte >> 4 & 0x03) + Encoding[layer].append(byte >> 6 ) + offset += 48 + + #read layers: + for layer in xrange(2): + for y in xrange(192): + if Encoding[layer][y] == 0:#Nothing + pass + elif Encoding[layer][y] == 1:#Normal + UseByte = AscDec(data[offset:offset+4]) + offset += 4 + x = 0 + while UseByte & 0xFFFFFFFF: + if UseByte & 0x80000000: + byte = ord(data[offset]) + offset += 1 + for _ in xrange(8): + if byte & 0x01: + Frame[layer][x][y] = True + x += 1 + byte >>= 1 + else: + x += 8 + UseByte <<= 1 + elif Encoding[layer][y] == 2:#Inverted + UseByte = AscDec(data[offset:offset+4]) + offset += 4 + x = 0 + while UseByte&0xFFFFFFFF: + if UseByte & 0x80000000: + byte = ord(data[offset]) + offset += 1 + for _ in range(8): + if not byte & 0x01: + Frame[layer][x][y] = True + x += 1 + byte >>= 1 + else: + x += 8 + UseByte <<= 1 + for n in range(256): + Frame[layer][n][y] = not Frame[layer][n][y] + elif Encoding[layer][y] == 3:#Raw/full + x = 0 + for _ in range(32): + byte = ord(data[offset]) + offset += 1 + for _ in range(8): + if byte & 0x01: + Frame[layer][x][y] = True + x += 1 + byte >>= 1 + + #Merges this frame with the previous frame if NewFrame isn't true: + if not NewFrame and PrevFrame: + if FrameMove[0] or FrameMove[1]:#Moves the previus frame if specified: + NewPrevFrame = [[[False for _ in xrange(192)] for _ in xrange(256)] for _ in xrange(2)] + + for y in range(192): + for x in range(256): + TempX = x+FrameMove[0] + TempY = y+FrameMove[1] + if 0 <= Tempx < 256 and 0 <= TempY < 192: + NewPrevFrame[0][TempX][TempY] = PrevFrame[0][x][y] + NewPrevFrame[1][TempX][TempY] = PrevFrame[1][x][y] + + PrevFrame = NewPrevFrame + + #Merge the frames: + for y in range(192): + for x in range(256): + Frame[0][x][y] = Frame[0][x][y] <> PrevFrame[0][x][y] + Frame[1][x][y] = Frame[1][x][y] <> PrevFrame[1][x][y] + + return Frame + +#Class TMB: +# +# With this class you can read a Flipnote TMB file into memory and get its attributes. +# You do so by using either instance.ReadFile() or instance.Read() of your instance of the TMB class. +# If you pass DecodeThumbnail=True to these functions, it will decode thumbnail. +# The TMB file is a file containing the metadata to a corresponding PPM file. +# +# After reading a TMB file, these values are available: +# +# - instance.FrameCount: The number of frames this flipnote consists of +# - instance.Locked: A boolean telling us wether it's locked or not +# - instance.ThumbnailFrameIndex: Which frame the thumbnail is(starting at 0) +# - instance.OriginalAuthorName: The name of the original author in unicode +# - instance.EditorAuthorName: The name of the (previous?) editing author in unicode +# - instance.Username: The username in unicode +# - instance.OriginalAuthorID: The ID of the original author +# - instance.EditorAuthorID: The ID of the last editor, the last user to save the file +# - instance.PreviousEditAuthorID: The ID of the previous editor +# - instance.OriginalFilename: The original filename +# - instance.CurrentFilename: The current filename +# - instance.Date: The date in seconds since January 1'st 2000 +# - instance.GetThumbnail(): The thumbnail stored in a 2D list of uint32 RGBA values. Will decode it first if not done so already +class TMB: + def __init__(self): + self.Loaded = False + self.Thumbnail = None + self.RawThumbnail = None + def ReadFile(self, path, DecodeThumbnail=False): + f = open(path, "rb") + ret = self.Read(f.read(), DecodeThumbnail) + f.close() + return ret + def Read(self, data, DecodeThumbnail=False): + if data[:4] <> "PARA" or len(data) < 0x6a0: + return False + + #Read the header: + self.AudioOffset = AscDec(data[4:8], True) + 0x6a0#only stored for self.Pack() + self.AudioLenght = AscDec(data[8:12], True)#only stored for self.Pack() + + self.FrameCount = AscDec(data[12:14], True) + 1 + self.Locked = ord(data[0x10]) & 0x01 == 1 + self.ThumbnailFrameIndex = AscDec(data[0x12:0x14], True)#which frame is in the thumbnnail + + #self.OriginalAuthorName = u"".join(unichr(AscDec(data[0x14+i*2:0x14+i*2+2], True)) for i in xrange(11)).split(u"\0")[0] + #self.EditorAuthorName = u"".join(unichr(AscDec(data[0x2A+i*2:0x2A+i*2+2], True)) for i in xrange(11)).split(u"\0")[0] + #self.Username = u"".join(unichr(AscDec(data[0x40+i*2:0x40+i*2+2], True)) for i in xrange(11)).split(u"\0")[0] + self.OriginalAuthorName = data[0x14:0x2A].decode("UTF-16LE").split(u"\0")[0] + self.EditorAuthorName = data[0x2A:0x40].decode("UTF-16LE").split(u"\0")[0] + self.Username = data[0x40:0x56].decode("UTF-16LE").split(u"\0")[0] + + self.OriginalAuthorID = data[0x56:0x5e][::-1].encode("HEX").upper() + self.EditorAuthorID = data[0x5E:0x66][::-1].encode("HEX").upper()#the last user to save the file + + self.OriginalFilenameC = data[0x66:0x78]#compressed + self.CurrentFilenameC = data[0x78:0x8a]#compressed + self.OriginalFilename = "%s_%s_%s.tmb" % (self.OriginalFilenameC[:3].encode("HEX").upper(), self.OriginalFilenameC[3:-2], str(AscDec(self.OriginalFilenameC[-2:], True)).zfill(3)) + self.CurrentFilename = "%s_%s_%s.tmb" % (self.CurrentFilenameC[:3].encode("HEX").upper(), self.CurrentFilenameC[3:-2], str(AscDec(self.CurrentFilenameC[-2:], True)).zfill(3)) + + self.PreviousEditAuthorID = data[0x8a:0x92][::-1].encode("HEX").upper()#don't know what this really is + + self.PartialFilenameC = data[0x92:0x9a]#compressed + + self.Date = AscDec(data[0x9a:0x9e], True)#in seconds since midnight 1'st january 2000 + + self.RawThumbnail = data[0xa0:0x6a0] + if DecodeThumbnail: + self.GetThumbnail()#self.Thumbnail[x][y] = uint32 RGBA + + self.Loaded = True + #return the results + return self + def WriteFile(self, path):#not implented + out = self.Pack() + if out: + f = open(path, "wb") + f.write(out) + f.close() + return True + else: + return False + def Pack(self, ppm=None):#not implented + if not self.Loaded: return False + + #realself = self + #if ppm: self = ppm + + out = ["PARA",#magic + DecAsc(self.AudioOffset-0x6a0, 4, True),#animation data size + DecAsc(self.AudioLenght, 4, True),#audio data size + DecAsc(self.FrameCount-1, 2, True),#frame count + "\x24\x00",#unknown + chr(self.Locked), "\0",#locked + DecAsc(self.ThumbnailFrameIndex, 2, True),#which frame is in the thumbnnail + self.OriginalAuthorName.encode("UTF-16LE") + "\0\0"*(11-len(self.OriginalAuthorName)),#Original Author Name + self.EditorAuthorName.encode("UTF-16LE") + "\0\0"*(11-len(self.EditorAuthorName)),#Editor Author Name + self.Username.encode("UTF-16LE") + "\0\0"*(11-len(self.Username)),#Username + self.OriginalAuthorID.decode("HEX")[::-1],#OriginalAuthorID + self.EditorAuthorID.decode("HEX")[::-1],#EditorAuthorID + self.OriginalFilenameC,#OriginalFilename + self.CurrentFilenameC,#CurrentFilename + self.PreviousEditAuthorID.decode("HEX")[::-1],#EditorAuthorID + self.PartialFilenameC,#PartialFilename + DecAsc(self.Date, 4, True),#Date in seconds + "\0\0",#padding + self.PackThumbnail()]#thumbnail + + return "".join(out) + def GetThumbnail(self, force=False): + if (not self.Thumbnail or force) and self.RawThumbnail: + if not self.RawThumbnail: + return False + + out = [[0 for _ in xrange(48)] for _ in xrange(64)] + palette = (0xFEFEFEFF,#0 + 0x4F4F4FFF,#1 + 0xFFFFFFFF,#2 + 0x9F9F9FFF,#3 + 0xFF0000FF,#4 + 0x770000FF,#5 + 0xFF7777FF,#6 + 0x00FF00FF,#7- + 0x0000FFFF,#8 + 0x000077FF,#9 + 0x7777FFFF,#A + 0x00FF00FF,#B- + 0xFF00FFFF,#C + 0x00FF00FF,#D- + 0x00FF00FF,#E- + 0x00FF00FF)#F- + + #8x8 tiling: + for ty in range(6): + for tx in range(8): + for y in range(8): + for x in range(0,8,2): + #two colors stored in each byte: + byte = ord(self.RawThumbnail[(ty*512+tx*64+y*8+x)/2]) + out[x+tx*8 ][y+ty*8] = palette[byte & 0xF] + out[x+tx*8+1][y+ty*8] = palette[byte >> 4] + + self.Thumbnail = out + return self.Thumbnail + def PackThumbnail(self, Exact=True, force=False):#more or less a private function for now + palette = (0xFEFEFEFF,#0 + 0x4F4F4FFF,#1 + 0xFFFFFFFF,#2 + 0x9F9F9FFF,#3 + 0xFF0000FF,#4 + 0x770000FF,#5 + 0xFF7777FF,#6 + 0x00FF00FF,#7- + 0x0000FFFF,#8 + 0x000077FF,#9 + 0x7777FFFF,#A + 0x00FF00FF,#B- + 0xFF00FFFF,#C + 0x00FF00FF,#D- + 0x00FF00FF,#E- + 0x00FF00FF)#F- + + + if not self.Thumbnail: + return self.RawThumbnail + else: + if Exact: + out = [] + + #8x8 tiling: + for ty in range(6): + for tx in range(8): + for y in range(8): + for x in range(0,8,2): + #two colors stored in each byte: + #pos = 0xa0+(ty*512+tx*64+y*8+x)/2 + p1 = palette.index(self.Thumbnail[x+tx*8 ][y+ty*8]) + p2 = palette.index(self.Thumbnail[x+tx*8+1][y+ty*8]) + out.append(chr(p2<<4 | p1)) + + self.RawThumbnail = "".join(out) + return self.RawThumbnail + else: + #not yet implented + return False + +#Function WriteImage: +# +# Writes a 2D list if uint32 RGBA values as a image files. +# Designed to work with PPM().Thumbnail or PPM().GetFrame(n) +# +# This function requires the PIl imaging module +def WriteImage(image, outputPath): + if not hasPIL or not image: return False + + out = [] + for y in xrange(len(image[0])): + for x in xrange(len(image)): + out.append(DecAsc(image[x][y], 4)) + + out = Image.fromstring("RGBA", (len(image), len(image[0])), "".join(out)) + + filetype = outputPath[outputPath.rfind(".")+1:] + out.save(outputPath, filetype) + + return True + +#this is just for experimenting with sound decoding: +#not even close to being finished +def DecodeSound(outputpath, data): + f = wave.open(outputpath, "wb") + f.setnchannels(1) + f.setsampwidth(2) + f.setframerate(32000) + for i in data: + i1 = ord(i)&0xF + i2 = (ord(i)>>4)&0xF + #Noise reduction: + #if i1&0x8: i1 = 0xF-(i1&0x7) + #if i2&0x8: i2 = 0xF-(i2&0x7) + i1 = i1&0x7 if i1&0x8 else 0xF-i1 + i2 = i2&0x7 if i2&0x8 else 0xF-i2 + f.writeframes(DecAsc(i1<<12, 2, True)*4) + f.writeframes(DecAsc(i2<<12, 2, True)*4) + f.close() + +#for testing during development: +if 1 == 2: + p = PPM() + print "loading ppm..." + p.ReadFile("PPMtests/test.ppm", (1, 1, 1)) + + print str(p.OriginalAuthorName), str(p.OriginalAuthorID) + print str(p.EditorAuthorName), str(p.EditorAuthorID) + + print "Dumping Thumbnail..." + WriteImage(p.Thumbnail, "thumbnail.png") + + print "Dumping frames..." + for i in xrange(p.FrameCount): + WriteImage(p.GetFrame(i), "frames/frame %i.png" % i) + + print "Dumping raw sounds..." + for i, data in enumerate(p.SoundData): + f = open("sound %i.bin" % i, "wb") + f.write(data) + f.close() + + print "Extracting sounds...." + for i, d in enumerate(p.SoundData): DecodeSound("out_%i.wav"%i, d) + +if __name__ == '__main__': + print " == PPM.py ==" + print " == by pbsds ==" + print " == v1.03 ==" + print + + if len(sys.argv) < 3: + print "Usage:" + print " PPM.py [] []" + print "" + print " :" + print " 0: Extracts the thumbnail to the file " + print " 1: Extracts the frame(s) to " + print " 2: Dumps the raw sound data files to the folder " + print " 3: Same as mode 2, but will also dump the experimentally decoded" + print " sounds." + print " 4: Prints out the metadata. Can also write it to which also" + print " supports unicode charactes." + print " Mode 0 and 4 supports TMB files aswell" + print " " + print " Only used in mode 1" + print " Set this to the exact frame you want to extract(starting at 1) and it" + print " will be saved as a file to ." + print " If not specified, it will extract all frames to the folder " + + sys.exit() + + import os, time + + if sys.argv[1] == "0": + print "Reading the flipnote file...", + if not os.path.isfile(sys.argv[2]): + print "Error!\nSpecified file doesn't exist!" + sys.exit() + flipnote = TMB().ReadFile(sys.argv[2], True) + if not flipnote: + print "Error!" + print "The given file is not a Flipnote PPM file or TMB file!" + sys.exit() + print "Done!" + + print "Dumping the thumbnail...", + WriteImage(flipnote.GetThumbnail(), sys.argv[3]) + print "Done!" + elif sys.argv[1] == "1": + if len(sys.argv) < 4: + print "Error!" + print " not specified!" + sys.exit() + + print "Reading the flipnote file...", + if not os.path.isfile(sys.argv[2]): + print "Error!\nSpecified file doesn't exist!" + sys.exit() + flipnote = PPM().ReadFile(sys.argv[2]) + if not flipnote: + print "Error!\nThe given file is not a Flipnote PPM file." + sys.exit() + print "Done!" + + + if len(sys.argv) < 5: + if not os.path.isdir(sys.argv[3]): + print "Error!\nThe specified directory doesn't exist!" + sys.exit() + + for i in xrange(flipnote.FrameCount): + print "Dumping frame #%i..." % (i+1), + WriteImage(flipnote.GetFrame(i), os.path.join(sys.argv[3], "frame %s.png" % str(i+1).zfill(3))) + print "Done!" + else: + try: + int(sys.argv[4]) + except: + print "Error!\nInvalid !" + sys.exit() + + if not (0 <= int(sys.argv[4])-1 < flipnote.FrameCount): + print "Error!\n is out of bounds!" + sys.exit() + + print "Dumping frame #%i..." % int(sys.argv[4]), + WriteImage(flipnote.GetFrame(int(sys.argv[4])-1), sys.argv[3]) + print "Done!" + elif sys.argv[1] in "23": + if len(sys.argv) < 4: + print "Error!" + print " not specified!" + sys.exit() + + print "Reading the flipnote file...", + if not os.path.isfile(sys.argv[2]): + print "Error!\nSpecified file doesn't exist!" + sys.exit() + flipnote = PPM().ReadFile(sys.argv[2], ReadFrames=False, ReadSound=True) + if not flipnote: + print "Error!\nThe given file is not a Flipnote PPM file." + sys.exit() + print "Done!" + + if not os.path.isdir(sys.argv[3]): + print "Error!\nThe specified directory doesn't exist!" + sys.exit() + + print "Dumping the raw sound data...", + for i, data in enumerate(flipnote.SoundData): + f = open(os.path.join(sys.argv[3], ("BGM.bin", "SFX1.bin", "SFX2.bin", "SFX3.bin")[i]), "wb") + f.write(data) + f.close() + print "Done!" + + if sys.argv[1] == "3": + print "Dumping the decoded sound data...", + for i, data in enumerate(flipnote.SoundData): + DecodeSound(os.path.join(sys.argv[3], ("BGM decoded.wav", "SFX1 decoded.wav", "SFX2 decoded.wav", "SFX3 decoded.wav")[i]), data) + print "Done!" + + print "Dumping the sound effect usage...", + f = open(os.path.join(sys.argv[3], "SFX usage.txt"), "w") + for i, (s1, s2, s3) in enumerate(flipnote.SFXUsage): + f.write("Frame %i:%s%s%s\n" % (i, " SFX1"*s1, " SFX2"*s2, " SFX3"*s3)) + f.close() + print "Done!" + elif sys.argv[1] == "4": + epoch = time.mktime(time.struct_time([2000, 1, 1, 0, 0, 0, 5, 1, -1])) + + if not os.path.isfile(sys.argv[2]): + print "Error!\nSpecified file doesn't exist!" + sys.exit() + + filetype = "ppm" if sys.argv[2][-3:] == "ppm" else "tmb" + flipnote = TMB().ReadFile(sys.argv[2]) if filetype == "tmb" else PPM().ReadFile(sys.argv[2], ReadFrames=False) + if not flipnote: + print "Error!\nThe given file is not a Flipnote PPM file or TMB file." + if len(sys.argv) >= 4: + f = open(sys.argv, "wb") + f.write("Error!\nThe given file is not a Flipnote PPM file or TMB file.") + f.close() + sys.exit() + + meta = [] + meta.append(u"Current filename: %s" % flipnote.CurrentFilename[:-3]+filetype) + meta.append(u"Original filename: %s" % flipnote.OriginalFilename[:-3]+filetype) + meta.append(u"Number of frames: %s" % flipnote.FrameCount) + meta.append(u"Locked: %s" % flipnote.Locked) + if filetype == "ppm": + meta.append(u"Frame speed: %s" % flipnote.Framespeed) + meta.append(u"Looped: %s" % flipnote.Looped) + meta.append(u"Thumbnail frame index: %i" % (flipnote.ThumbnailFrameIndex+1)) + meta.append(u"Original author: %s" % flipnote.OriginalAuthorName) + meta.append(u"Editor author: %s" % flipnote.EditorAuthorName) + meta.append(u"Username: %s" % flipnote.Username) + meta.append(u"Original author ID: %s" % flipnote.OriginalAuthorID) + meta.append(u"Editor author ID: %s" % flipnote.EditorAuthorID) + meta.append(u"Date(seconds since 1'st Jan 2000): %s" % flipnote.Date) + meta.append(time.strftime(u"Date: %H:%M %d/%m-%Y", time.gmtime(epoch+flipnote.Date)) + " (faulty)") + + newline = "\n" + if sys.platform in ("win32", "cygwin"): newline = "\r\n" + elif sys.platform in ("darwin"): newline = "\r" + + print newline.join(meta).encode('ascii', 'ignore') + + if len(sys.argv) >= 4: + f = open(sys.argv[3], "wb") + f.write(newline.join(meta).encode("UTF-8")) + f.close() + else: + print "Error!\nThere's no such mode." \ No newline at end of file diff --git a/Hatenatools/ReadMe.txt b/Hatenatools/ReadMe.txt new file mode 100644 index 0000000..e4f8127 --- /dev/null +++ b/Hatenatools/ReadMe.txt @@ -0,0 +1,30 @@ + == Hatenatools == + == By pbsds == + == aka Peder Bergebakken Sundt == + +--VERSIONS-- +PPM.py: v1.03 +UGO.py: v0.09 +NTFT.py: v0.05 + +--DESCRIPTION-- +PPM.py can read the metadata, thumbnail, frames and the sound of Flipnote Studio files: .ppm +It can also handle .tmb files. + +UGO.py can read and write UGO files, and modify their content. It can also convert UGO files +to and from the XML format + +NTFT.py can read ntft image files and convert them to image files +Warning, it's still in a very crude state. Error will occour + +These are all tested and written in python 2.7 +These are all designed to be usable both as python modules and as stand alone applications. + +--LICENCE-- +Free to use. +They are free to modify as you wish as long you give credit to me, pbsds / Peder Bergebakken Sundt + +--CREDIT-- +-Steven for most of the documentation on DSiBrew and his frame decoding example on his talkpage +-Remark for helping me understanding the 8x8 tiling on the preview images. +-Austin Burk and Jsafive for supplying .tmb and .ugo files \ No newline at end of file diff --git a/Hatenatools/UGO.py b/Hatenatools/UGO.py new file mode 100644 index 0000000..8c0e214 --- /dev/null +++ b/Hatenatools/UGO.py @@ -0,0 +1,573 @@ +#This class reads and writes UGO files. +#cal also export and import it in the xml format +#Extended functionality added by PPM.py from http://pbsds.net/projects/ppm +import sys, os +from base64 import b64encode, b64decode +import xml.etree.ElementTree as ET + +try: + import PPM + HasPPM = True +except ImportError: + HasPPM = False + + +#helpers: +def AscDec(ascii, LittleEndian=False):#Converts a ascii string into a decimal + ret = 0 + l = map(ord, ascii) + if LittleEndian: l.reverse() + for i in l: + ret = (ret<<8) | i + return ret +def DecAsc(dec, length=None, LittleEndian=False):#Converts a decimal into an ascii string of chosen length + out = [] + while dec <> 0: + out.insert(0, dec&0xFF) + dec >>= 8 + #"".join(map(chr, out)) + + if length: + if len(out) > length: + #return "".join(map(chr, out[-length:])) + out = out[-length:] + if len(out) < length: + #return "".join(map(chr, [0]*(length-len(out)) + out)) + out = [0]*(length-len(out)) + out + + if LittleEndian: out.reverse() + return "".join(map(chr, out)) +def zipalign(length, r=4): + return length + (4 - length % r) if length % r else length +def indentXML(elem, level=0):#borrowed from: http://effbot.org/zone/element-lib.htm#prettyprint + i = "\n" + level*"\t" + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + "\t" + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + indentXML(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + +#class UGO: +class UGO: + def __init__(self): + self.Loaded = False + def ReadFile(self, path): + f = open(path, "rb") + ret = self.Read(f.read()) + f.close() + return ret + def Read(self, data): + #Filestructure: + # - Header + # - Table of Contents + # - Padding, normally of length 2 + # - Extra Data + # - Padding + #The padding seems to do zipaligning to a length of 4 + global HasPPM + + #Header: + if data[:4] <> "UGAR": return False#The file isn't a UGO file + Sections = AscDec(data[ 4: 8], True)#could also be version + if Sections >= 1: + self.TableLength = AscDec(data[ 8:12], True) + if Sections >= 2: + self.ExtraLength = AscDec(data[12:16], True) + if Sections > 2: + print "Warning: This UGO file has more than the 2 known sections:",Sections + print "Please send this UGO file to pbsds over at pbsds.net" + print "This file could possibly be read incorrectly..." + headerlength = 8 + Sections*4 + + #Read table of contents: + #A table where the rows are seperated with newlines, and colons with tabs + if Sections >= 1: + TableOfContents = tuple((i.split("\t") for i in data[headerlength:headerlength+self.TableLength].split("\n"))) + else: + TableOfContents = [] + + #Extra data: + if Sections >= 2: + ExtraData = data[zipalign(headerlength+self.TableLength) : zipalign(headerlength+self.TableLength)+self.ExtraLength] + + #Read data: + self.Items = [] + pos = 0#Extra Data position + tmbcount = 1#used if HasPPM is False + ntftcount = 1#used if no label + names = [] + for i in TableOfContents: + type = int(i[0]) + if type == 0:#layout + #I've always seen just numbers here. + #The amount of numbers also differ. + #maybe related to color scheme? or the button scheme/layout + + #all pages containing TMBs have so far been: i = ["0", "2", "1"] + self.Items.append(("layout", map(int, i[1:]))) + continue + elif type == 1:#Text on topscreen + num = int(i[1])#always seen as 0, unknown purpose + labels = [b64decode(i[n]).decode("UTF-16LE") for n in xrange(2,7)]#5 labels probably one label for each line of text + + self.Items.append(("topscreen text", labels, num)) + continue + elif type == 2:#catogories(like "reccomended" and "new flipnotes" and "most popular") + #this one may change greatly depending on what layout is set in type==0 + + link = i[1] + label = b64decode(i[2]).decode("UTF-16LE") + selected = int(i[3]) <> 0#bool + + self.Items.append(("category", link, label, selected)) + continue + elif type == 3:#POST button? "Post Flipnote" + pass + elif type == 4:#Button + link = i[1] + trait = int(i[2]) + label = b64decode(i[3]).decode("UTF-16LE") + other = i[4:]#varies + + #extra data + file = None#== ("filename", "filedata") + if trait < 100 and ExtraData: + if ExtraData == "\x20":#empty + pass#no files + elif ExtraData[pos:pos+4] == "PARA":#tmb file + file = ExtraData[pos:pos+0x6A0] + pos += 0x6A0 + + if HasPPM: + tmb = PPM.TMB().Read(file) + name = tmb.CurrentFilename[:-4] + del tmb + else: + name = "embedded tmb #%i" % tmbcount + tmbcount += 1 + + if name+".tmb" in names: + j = 2 + while "%s_%i.tmb" % (name, j) not in names: + j += 1 + name = "%s_%i" % (name, j) + + file = (name+".tmb", file) + names.append(name+".tmb") + else:#ntft icon + name = label.encode("ascii", "ignore") + if not name: + name = "nameless ntft %i" % ntftcount + ntftcount += 1 + + if name+".ntft" in names: + j = 2 + while "%s_%i.ntft" % (name, j) not in names: + j += 1 + name = "%s_%i" % (name, j) + + file = (name+".ntft", ExtraData[pos:pos+2048]) + names.append(name+".ntft") + pos += 2048 + + self.Items.append(("button", trait, label, link, other, file)) + + + # if subtype == 3:#flipnote + # tmb = PPM.TMB().Read(ExtraData[pos:pos+0x6A0]); pos += 0x6A0 + # unknown1 = i[3]#empty + # stars = int(i[4])#not sure + # unknown2 = map(int, i[5:8])#unknown = [765, 573, 0] + + # self.Items.append(("flipnote", link, tmb, stars, unknown1, unknown2)) + # continue + # elif subtype == 100: pass + # elif subtype == 101: pass + # elif subtype == 102: pass + # elif subtype == 104:#list item? like mails and announcements + # label = b64decode(i[3]).decode("UTF-16LE") + # unknown = i[4] + # num = int(i[5])#only seen as 0 + + # # self.Items.append(("list item?", link, label, unknown, num)) + # # continue + # pass + # elif subtype == 115:#Labeled button link(size of a flipnote thumbnail, commonly "next page") + # label = b64decode(i[3]).decode("UTF-16LE") + # unknown = i[4:6] + # self.Items.append(("thumbnail link", link, label, unknown)) + # continue + # elif subtype == 117: pass + continue + + #if not recognized: + self.Items.append(("unknown", i)) + print "Unknown UGO item discovered:", i + self.Loaded = True + return self + def WriteFile(self, path): + if self.Loaded: + out = self.Pack() + if out: + f = open(path, "wb") + f.write(out) + f.close() + return True + else: + return False + def Pack(self): + if not self.Loaded: return False + + Header = ["UGAR", None] + TableOfContents = [] + ExtraData = [] + + #Encode data: + for i in self.Items: + if i[0] == "unknown": + TableOfContents.append("\t".join(i[1])) + elif i[0] == "layout":#0 + TableOfContents.append("\t".join(["0"] + map(str, list(i[1])))) + elif i[0] == "topscreen text":#1 + labels, num = i[1:] + + num = str(num) + for i in xrange(5): + labels[i] = b64encode(labels[i].encode("UTF-16LE")) + + TableOfContents.append("\t".join(("1", num, labels[0], labels[1], labels[2], labels[3], labels[4]))) + elif i[0] == "category":#2 + link, label, selected = i[1:] + + label = b64encode(label.encode("UTF-16LE")) + selected = str(1*selected) + + TableOfContents.append("\t".join(("2", link, label, selected))) + elif i[0] == "post":#not yet implented + pass + elif i[0] == "button": + trait, label, link, other, file = i[1:] + + trait = str(trait) + label = b64encode(label.encode("UTF-16LE")) + + TableOfContents.append("\t".join(["4", link, trait, label] + list(other))) + + if file: + ExtraData.append(file[1]) + else: + print "Unrecognized entry in self.Items:", i + TableOfContents = "\n".join(TableOfContents) + ExtraData = "".join(ExtraData) + + #Format data: + Sections = 0 + if TableOfContents: + Sections += 1 + Header.append(DecAsc(len(TableOfContents), 4, True)) + + #padding/zipaligning + if len(TableOfContents) % 4: + TableOfContents += "\0" * (4 - len(TableOfContents) % 4) + if ExtraData: + Sections += 1 + Header.append(DecAsc(len(ExtraData), 4, True)) + + #padding/zipaligning + if len(ExtraData) % 4: + ExtraData += "\0" * (4 - len(ExtraData) % 4) + Header[1] = DecAsc(Sections, 4, True) + + #Zip up and send the file: + return "".join(Header) + TableOfContents + ExtraData + #XML + def WriteXML(self, xmlname="content.ugoxml", folder="content.ugoxml embedded"):#WIP + if not self.Loaded: return False + + path, xmlname = os.path.split(xmlname) + ugo_xml = ET.Element("ugo_xml") + files = [] + + for i in self.Items: + if i[0] == "unknown": + elem = ET.SubElement(ugo_xml, "unknown", type=i[1][0]) + for value in i[1][1:]: + ET.SubElement(elem, "value").text = value + continue + elif i[0] == "layout":#0 + elem = ET.SubElement(ugo_xml, "layout") + for value in i[1]: + ET.SubElement(elem, "value").text = str(value) + continue + elif i[0] == "topscreen text":#1 + elem = ET.SubElement(ugo_xml, "title") + labels, num = i[1:] + + for label in labels: + ET.SubElement(elem, "label").text = label + + ET.SubElement(elem, "num").text = str(num) + continue + elif i[0] == "category":#2 + elem = ET.SubElement(ugo_xml, "category") + link, label, selected = i[1:] + + ET.SubElement(elem, "label").text = label + ET.SubElement(elem, "address").text = link + ET.SubElement(elem, "selected").text = str(selected).lower() + elif i[0] == "post":#3 + pass#not yet implented/parsed + elif i[0] == "button":#4 + elem = ET.SubElement(ugo_xml, "button") + trait, label, link, other, file = i[1:] + + ET.SubElement(elem, "label").text = label + ET.SubElement(elem, "address").text = link + ET.SubElement(elem, "trait").text = str(trait)#todo: add names + + for n, value in enumerate(other): + entry = ET.SubElement(elem, "value") + entry.text = value + if n == 0 and trait == 3: + entry.attrib["tip"] = "stars" + + if file: + ET.SubElement(elem, "embedded_file").text = os.path.join(folder, file[0]) + files.append((os.path.join(folder, file[0]), file[1])) + + #intend + indentXML(ugo_xml) + + #pack xml + files.append((xmlname, ET.tostring(ugo_xml, encoding="UTF-8"))) + + #write files + if not os.path.isdir(os.path.join(path, folder)): + os.mkdir(os.path.join(path, folder)) + for name, data in files: + f = open(os.path.join(path, name), "wb") + f.write(data) + f.close() + def ReadXML(self, xmlfile, silent=True): + ugo_xml = ET.parse(xmlfile).getroot() + xmlpath = os.path.split(xmlfile)[0] + + Items = [] + for elem in ugo_xml: + if elem.tag == "unknown": + if "type" not in elem.attrib: + if not silent: print "Invalid formatting. without \"type\" attribute" + return False + values = [elem.attrib["type"]] + for value in elem: + if value.tag <> "value": + if not silent: print "Invalid formatting. <%s> found within " % value.tag + return False + values.append(value.text if value.text else "") + + Items.append(("unknown", values)) + elif elem.tag == "layout":#0 + values = [] + for value in elem: + if value.tag <> "value": + if not silent: print "Invalid formatting. <%s> found within " % value.tag + return False + if not value.text.isdigit(): + if not silent: print "Invalid entry. in is not a number" % value.tag + return False + values.append(int(value.text)) + Items.append(("layout", values)) + elif elem.tag == "title":#1 + labels = ["", "", "", "", ""] + num = 0 + pos = 0 + numset = False + + for value in elem: + if value.tag not in ("label", "num"): + if not silent: print "Invalid formatting. <%s> found within " % value.tag + return False + if value.tag == "label": + if pos >= 5: + if not silent: print "Invalid formatting. More than 5 <labels> in <title>" + return False + if value.text: labels[pos] = value.text + pos += 1 + elif value.tag == "num": + if numset: + if not silent: print "Invalid formatting. Multible <num> in <title>" + return False + if not value.text.isdigit(): + if not silent: print "Invalid entry. <num> in <title> is not a number!" + return False + num = int(value.text) + numset = True + + Items.append(("topscreen text", labels, num)) + elif elem.tag == "category":#2 + link = None + label = None + selected = None + + for value in elem: + if value.tag not in ("label", "address", "selected"): + if not silent: print "Invalid formatting. <%s> found within <category>" % value.tag + return False + + if value.tag == "address": + if isinstance(link, str): + if not silent: print "Invalid formatting. multible <address> within <category>" + return False + link = value.text if value.text else "" + elif value.tag == "label": + if isinstance(label, str): + if not silent: print "Invalid formatting. multible <label> within <category>" + return False + label = value.text if value.text else "" + elif value.tag == "selected": + if selected in (True, False): + if not silent: print "Invalid formatting. multible <selected> within <category>" + return False + selected = value.text[0].lower() in "t1" + + Items.append(("category", link, label, selected)) + elif elem.tag == "post":#3 not yet implented + pass + elif elem.tag == "button":#4 + trait = None#todo: add names + label = None + link = None + other = [] + file = None + + for value in elem: + if value.tag not in ("label", "address", "trait", "value", "embedded_file"): + if not silent: print "Invalid formatting. <%s> found within <button>" % value.tag + return False + + if value.tag == "label": + if isinstance(label, str): + if not silent: print "Invalid formatting. Multible <label> within <button>" + return False + label = value.text if value.text else "" + elif value.tag == "address": + if isinstance(link, str): + if not silent: print "Invalid formatting. Multible <address> within <button>" + return False + link = value.text if value.text else "" + elif value.tag == "trait":#todo: add names + if isinstance(trait, str): + if not silent: print "Invalid formatting. Multible <trait> within <button>" + return False + if not value.text.isdigit(): + if not silent: print "Invalid entry. <trait> in <button> is not a number" + return False + trait = int(value.text) + elif value.tag == "value": + other.append(value.text if value.text else "") + elif value.tag == "embedded_file": + if file <> None: + if not silent: print "Invalid formatting. Multible <embedded_file> within <button>" + return False + + path = os.path.join(xmlpath, value.text) + if not os.path.isfile(path): + if not silent: print "Invalid entry. Embedded file \"%s\" not found!" % value.text + print path + return False + + + f = open(path, "rb") + file = (os.path.split(value.text)[1], f.read()) + f.close() + + if None in (trait, label, link): + if not silent: print "Invalid formatting. <button> lacks either a <trait>, a <address> or a <label>" + return False + + Items.append(("button", trait, label, link, other, file)) + else: + if not silent: + print "Invalid formatting: <%s> found within <ugo_xml>" % elem.tag + + self.Items = Items + self.Loaded = True + return self + +if __name__ == "__main__": + print " == UGO.py ==" + print " == by pbsds ==" + print " == v0.90 ==" + print + + if len(sys.argv) < 3: + print "Usage:" + print " UGO.py <mode> <input> [<output> [<foldername>]]" + print "" + print " <Mode>:" + print " -d: Converts the UGO file in <input> to a UGOXML file with the same" + print " name, unless <output> is specified. Any embedded files will be" + print " written to a folder called UGOXML-filename + \" embedded\" unless" + print " <foldername> is given." + print " <foldername> is relative to the XML." + print " -e: Converts the UGOXML file in <input> to a UGO file with the same" + print " name, unless <output> is specified." + sys.exit() + + mode = sys.argv[1] + if mode not in ("-d", "-e"): + print "Invalid <mode> given" + sys.exit() + if mode == "-d": + input = sys.argv[2] + output = sys.argv[3] if len(sys.argv) >= 4 else sys.argv[2]+"xml" + foldername = sys.argv[4] if len(sys.argv) >= 5 else os.path.split(output)[1] + " embedded" + + print "Reading %s..." % os.path.split(input)[1] + ugo = UGO().ReadFile(input) + if not ugo: + print "Error!\n The given file is not a UGO file!" + sys.exit() + print "Done!" + + print "Writing XML..." + ugo.WriteXML(output, foldername) + + print "Done!\n\nHave a nice day!" + if mode == "-e": + input = sys.argv[2] + output = sys.argv[3] if len(sys.argv) >= 4 else ".".join(sys.argv[2].split(".")[:-1]) + ".ugo" + + print "Reading %s..." % os.path.split(input)[1] + try: + ugo = UGO().ReadXML(input, False) + except EL.ParseError: + print "Error!\nThe given file is not in the XML format" + ugo = False + if not ugo: + #it prints sufficient errormessages + #print "Error!\n The given file is not a UGO file!" + sys.exit() + print "Done!" + + print "Writing UGO..." + ugo.WriteFile(output) + print "Done" + + + + + + + + + + + diff --git a/Hatenatools/__init__.py b/Hatenatools/__init__.py new file mode 100644 index 0000000..054c2b9 --- /dev/null +++ b/Hatenatools/__init__.py @@ -0,0 +1,3 @@ +from PPM import PPM, TMB +from UGO import UGO +from NTFT import NTFT \ No newline at end of file diff --git a/Hatenatools/hatenatools project page.url b/Hatenatools/hatenatools project page.url new file mode 100644 index 0000000..408b915 --- /dev/null +++ b/Hatenatools/hatenatools project page.url @@ -0,0 +1,5 @@ +[{000214A0-0000-0000-C000-000000000046}] +Prop3=19,2 +[InternetShortcut] +URL=http://pbsds.net/projects/hatenatools +IDList= diff --git a/database/Creators/913304C0CC74CC1F/74CC1F_0D376FC60C9DC_000.ppm b/database/Creators/913304C0CC74CC1F/74CC1F_0D376FC60C9DC_000.ppm new file mode 100644 index 0000000..31a743b Binary files /dev/null and b/database/Creators/913304C0CC74CC1F/74CC1F_0D376FC60C9DC_000.ppm differ diff --git a/database/Creators/913304C0CC74CC1F/flipnotes.dat b/database/Creators/913304C0CC74CC1F/flipnotes.dat new file mode 100644 index 0000000..bb5f7e2 --- /dev/null +++ b/database/Creators/913304C0CC74CC1F/flipnotes.dat @@ -0,0 +1 @@ +74CC1F_0D376FC60C9DC_000 1 5 0 0 0 0 \ No newline at end of file diff --git a/database/new_flipnotes.dat b/database/new_flipnotes.dat new file mode 100644 index 0000000..18715a2 --- /dev/null +++ b/database/new_flipnotes.dat @@ -0,0 +1 @@ +913304C0CC74CC1F 74CC1F_0D376FC60C9DC_000 \ No newline at end of file diff --git a/hatena.py b/hatena.py new file mode 100644 index 0000000..31d57f9 --- /dev/null +++ b/hatena.py @@ -0,0 +1,325 @@ +from twisted.web import static, resource +from twisted.internet import reactor +import sys, os, atexit, glob, imp + +from Hatenatools import *#UGO, PPM, TMB and NTFT +ServerLog = None#Is set to the log class by server.py + +Silent = False +def Log(request, path=None): + if not path: + path = "\"%s\"" % request.path + ServerLog.write("%s requested %s" % (request.getClientIP(), path), Silent) + +#Responses: +class AccessDenied(resource.Resource): + isLeaf = True + def render(self, request): + ServerLog.write("%s got 403 when requesting \"%s\"" % (request.getClientIP(), request.path), Silent) + + request.setResponseCode(403) + return "403 - Access denied\nThis proxy is only for Flipnote Hatena for the DSi." +AccessDenied = AccessDenied() +class NotFound(resource.Resource): + isLeaf = True + def render(self, request): + args = "&".join(("%s=%s" % (i, request.args[i][0]) for i in request.args)) + path = ("%s?%s" % (request.path, args)) if args else request.path + + ServerLog.write("%s got 404 when requesting \"%s\"" % (request.getClientIP(), path), Silent) + request.setResponseCode(404) + return "404 - Not Found\nThis proxy is only for Flipnote Hatena for the DSi." +NotFound = NotFound() + +#Root structure: +class Root(resource.Resource):#filters out non-hantena clients + isLeaf = False + def __init__(self): + resource.Resource.__init__(self) + + self.dsResource = ds() + self.cssResource = static.File("hatenadir/css/") + self.imagesResource = static.File("hatenadir/images/") + def getChild(self, name, request): + if "x-dsi-sid" not in request.getAllHeaders(): + return AccessDenied + + if name == "ds": + return self.dsResource + elif name == "css": + return self.cssResource + elif name == "images": + return self.imagesResource + elif name == "": + return self + + #return NotFound + return self + def render(self, request): + Log(request, "root") + return "Welcome to hatena.pbsds.net!\nThis is in early stages, so please don't expect too much." +class ds(resource.Resource):#child of Root + isLeaf = False + def __init__(self): + resource.Resource.__init__(self) + + self.region = UgoRoot() + self.regions = ("v2-xx", "v2-eu", "v2-us", "v2-jp") + def getChild(self, name, request): + if name in self.regions: + return self.region + elif name == "": + return self + return NotFound + def render(self, request): + Log(request) + return "ds desu" +class UgoRoot(resource.Resource):#child of ds. (v2-xx) + isLeaf = False + def __init__(self): + resource.Resource.__init__(self) + + LoadHatenaStructure(self) + def getChild(self, name, request): + if name == "": + return self + return NotFound + def render(self, request): + Log(request, "ugoroot") + return "UgoRoot desu" + +#UgoRoot filestructure: +class FileResource(resource.Resource): + isLeaf = True + def __init__(self, filepath, Store=False): + resource.Resource.__init__(self) + + self.Store = Store + + if Store: + f = open(filepath, "rb") + self.file = f.read() + f.close() + else: + self.file = filepath + + self.html = filepath.split(".")[-1][:3] == "htm" + def render(self, request): + path = "/".join(request.path.split("/")[3:]) + Log(request, path) + + if self.html: + #request.responseHeaders.setRawHeaders('content-type', ['text/html']) + pass + else: + request.responseHeaders.setRawHeaders('content-type', ['text/plain']) + + if self.Store: + return self.file + else: + #f = open(self.file, "rb") + #ret = f.read() + #f.close() + #return ret + + return static.File(self.file).render(request) +class UGOXMLResource(resource.Resource): + isLeaf = True + def __init__(self, filepath): + resource.Resource.__init__(self) + + + self.ugofile = UGO().ReadXML(filepath, False) + self.ugofile = self.ugofile.Pack() + def render(self, request): + path = "/".join(request.path.split("/")[3:]) + Log(request, path) + + #page = int(request.args["page"][0]) if "page" in request.args else 1 + request.responseHeaders.setRawHeaders('content-type', ['text/plain']) + return self.ugofile +class FolderResource(resource.Resource): + isLeaf = False + def getChild(self, name, request): + if name == "": + return self + return NotFound + def render(self, request): + path = "/".join(request.path.split("/")[3:]) + Log(request, path) + return "This is a folder, but I'm to lazy to list the contents..." +def LoadHatenaStructure(Resource, path="hatenadir/ds/v2-xx", root = True): + for i in glob.glob("%s\*" % path): + if os.path.isfile(i): + filename = os.path.basename(i) + filetype = filename.split(".")[-1].lower() + + if filetype == "ugoxml": + Resource.putChild(filename[:-3], UGOXMLResource(i)) + elif filetype == "py": + try: + #sys.path.append(os.path.abspath(os.path.dirname(i))) + # #pyfile = __import__(filename[:-3]) + #pyfile = importlib.import_module(filename[:-3]) + #sys.path.pop(-1) + #pyfile = imp.load_module(i) + + pyfile = imp.load_source("pyfile", os.path.abspath(i)) + except ImportError as err: + pyfile = None + print "Error!" + print "Failed to import the python file \"%s\"" % i + print err + + if pyfile: + Resource.putChild(filename[:-3], pyfile.PyResource()) + elif filetype == "pyc": + pass#ignore + else: + Resource.putChild(filename, FileResource(os.path.join(path, filename))) + elif os.path.isdir(i): + if os.path.basename(i)[:2] <> "__": + folder = FolderResource() + LoadHatenaStructure(folder, os.path.join(path, os.path.basename(i)), False) + Resource.putChild(os.path.basename(i), folder) + +#Flipnote handler: +class Database: + def __init__(self): + #read: + f = open("database/new_flipnotes.dat", "rb")#contains the newest 5000 flipnotes + self.Newest = [tuple(i.split("\t")) for i in f.read().split("\n")]#[i] = [creatorID, filename] + self.new = False#True when new flipnotes has been uploaded + f.close() + + self.Creator = {}#to store creator info updates before writing to disk. Creator[id][n] = [filename, views, stars, green stars, red stars, blue stars, purple stars, Channel] + + reactor.callLater(60*3, self.flusher) + atexit.register(self.write) + + #to check if an update is neccesary: + self.Views = 0 + self.Stars = 0 + def flusher(self):#Automatically flushes the files every 3 minutes and trims down memory usage + reactor.callLater(60*3, self.flusher) + + #trim newest: + if len(self.Newest) > 5000: + self.Newest = self.Newest[:5000] + + self.write() + def write(self): + if self.new: + f = open("database/new_flipnotes.dat", "wb") + f.write("\n".join(("\t".join(i) for i in self.Newest))) + f.close() + self.new = False + + for ID in self.Creator.keys(): + f = open("database/Creators/%s/flipnotes.dat" % ID, "wb") + f.write("\n".join(("\t".join(map(str, i)) for i in self.Creator[ID]))) + f.close() + del self.Creator[ID] + #helpers: + def FlipnotePath(self, CreatorID, filename): + return "database/Creators/%s/%s" % (CreatorID, filename) + def CreatorExists(self, CreatorID): + return os.path.exists("database/Creators/" + CreatorID) or (CreatorID in self.Creator) + def FlipnoteExists(self, CreatorID, filename): + return os.path.exists("database/Creators/%s/%s.ppm" % (CreatorID, filename)) + #interface: + def GetCreator(self, CreatorID, Load=False):#Load holds it in memory, use this when making changes or reading him often + if CreatorID in self.Creator: + return self.Creator[CreatorID] + else: + if not os.path.exists("database/Creators/" + CreatorID): + return None + + f = open("database/Creators/%s/flipnotes.dat" % CreatorID, "rb") + ret = [i.split("\t") for i in f.read().split("\n")] + f.close() + + if Load: + self.Creator[CreatorID] = ret + + return ret + def GetFlipnote(self, CreatorID, filename):#[filename, views, stars, green stars, red stars, blue stars, purple stars, Channel] + for i in (self.GetCreator(CreatorID) or []): + if i[0] == filename: + return i + return False + def AddFlipnote(self, content, Channel=""): + tmb = TMB().Read(content) + if not tmb: + return False + + #CreatorID = tmb.Username + CreatorID = tmb.EditorAuthorID + filename = tmb.CurrentFilename[:-4] + del tmb + + if os.path.exists("database/Creators/%s/%s.ppm" % (CreatorID, filename)):#already exists + return False + + #add to database: + self.new = True + self.Newest.insert(0, (CreatorID, filename)) + + if not self.GetCreator(CreatorID, True): + self.Creator[CreatorID] = [[filename, 0, 0, 0, 0, 0, 0, Channel]] + else: + self.Creator[CreatorID].append([filename, 0, 0, 0, 0, 0, 0, Channel]) + + #write flipnote to file: + if not os.path.isdir("database/Creators/" + CreatorID): + os.mkdir("database/Creators/" + CreatorID) + f = open("database/Creators/%s/%s.ppm" % (CreatorID, filename), "wb") + f.write(content) + f.close() + + return CreatorID, filename + def AddView(self, CreatorID, filename): + for i, flipnote in enumerate(self.GetCreator(CreatorID, True) or []): + if flipnote[0] == filename: + self.Creator[CreatorID][i][1] = int(flipnote[1]) + 1 + self.Views += 1 + return True + return False + def AddStar(self, Creator, filename, amount=1): + for i, flipnote in enumerate(self.GetCreator(CreatorID, True) or []): + if flipnote[0] == filename: + self.Creator[CreatorID][i][2] = int(flipnote[2]) + amount + self.Stars += 1 + return True + return False +Database = Database() + +#when loading: +def Setup(): + print "Setting up hatena...", + root = Root() + print "Done!" + return root + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hatenadir/css/ds/basic.css b/hatenadir/css/ds/basic.css new file mode 100644 index 0000000..d24c935 --- /dev/null +++ b/hatenadir/css/ds/basic.css @@ -0,0 +1,830 @@ +@charset "UTF-8"; +table.profbox { + background-color: #82c300; + color: #ffffff; +} +div.prof_out { + margin: 0 11px 0 11px; + background-color: #82c300; + border-style: solid; + border-width: 0 1px 0 1px; + border-color: #82c300; +} +span.more-f{ + color: #ffffff; +} +div.notice2 { + margin:0 11px 5px 11px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#82c300; + background-color:#fbfbfb; +} +span.url{ + text-decoration:underline; +} +span.comptxt { + color:#ff8a00; +} +span.star0 { + color:#fdc556; +} +span.star1 { + color:#138200; +} +span.star2 { + color:#ff455c; +} +span.star3 { + color:#00b0f9; +} +span.star4 { + color:#a411b9; +} +span.star0c { + color:#fdc556; +} +span.star1c { + color:#138200; +} +span.star2c { + color:#ff455c; +} +span.star3c { + color:#00b0f9; +} +span.star4c { + color:#a411b9; +} +span.orange{ + color: #ff8d45; +} +span.green { + color: #82c300; +} +span.gray{ + color: #666666; +} +span.white { + color:#FFFFFF; +} +html { + margin:0; + padding:0; +} +body { + color:#333333; + margin:0; + padding:0; +} +div.title { + margin:0 7px 0 7px; + padding:7px 5px 7px 5px; + border-style:solid; + border-width:0 0 1px 0; + border-color:#CCCCCC; +} +div.description { + margin:0 7px 0 7px; + padding:7px 5px 7px 5px; + border-style:solid; + border-width:0 0 1px 0; + border-color:#CCCCCC; +} +div.hr { + border-style:solid; + border-width:0 0 1px 0; + border-color:#CCCCCC; +} +div.hr-tab { + border-style:solid; + border-width:0 0 1px 0; + border-color:#82c300; +} +span.timestamp { + color:#999999; +} +a.timestamp { + color:#999999; +} +a.violation { + color:#333333; +} +div.box { + padding:7px; + border-style:solid; + border-width:1px; + border-color:#CCCCCC; + background-color:#FFFFFF; +} +div.more { + margin:7px 0 0 0; +} +div.pager { + padding:5px; +} +div.num { + margin:5px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#CCCCCC; + background-color:#FFFFFF; +} +span.new { + color: #cc0000; +} +span.price { + color: #92cb22; +} +span.date { + color: #cccccc; +} +table.detail{ + margin:0 7px 0 7px; +} +table.fullsize { + margin:0; +} +div.item-term { + padding:3px 0 3px 5px; +} +div.item-value { + padding:3px 5px 3px 0; +} +div.item-text { + padding:3px 5px 3px 15px; +} +div.item-tcom { + padding: 3px 5px 0 5px; +} +div.item-com { + padding: 0 5px 0 5px; +} +div.td-body { + padding:3px 5px 3px 5px; +} +div.comlist { + background-color:#FFFFFF; +} +div.comment-text { + padding:3px 5px 5px 15px; +} +div.comment-memo { + padding:0 0 0 15px; +} +div.staff { + color:#FFFFFF; + background-color:#1a1a1a; +} +a.staffnam { + color:#FFFFFF; +} +div.notice { + width:226px; + margin:7px 7px 3px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#BCCDED; + background-color:#E5EEFE; +} +div.colorstar { + margin:7px 0 7px 0; +} +div.code-box{ + width:226px; + margin:7px 0 0 0; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#BCCDED; + background-color:#E5EEFE; +} +div.code-data{ + margin:7px 0 7px 0; +} +div.mail-reply { + width:226px; + margin:7px 7px 2px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#BCCDED; + background-color:#E5EEFE; +} +div.lett_txt { + margin: 0 11px 0 11px; + border-style:solid; + border-width:0 2px 0 2px; + border-color:#92d300; + background-color:#ffffff; +} +div.lett_to { + margin: 0 11px 0 11px; + background-color:#92d300; + color:#ffffff; + padding: 1px 0 6px 5px; +} +div.pad0{ + padding: 0 0 0 0; +} +div.pad5t{ + padding: 5px 0 0 0; +} +div.pad5b{ + padding: 0 0 5px 0; +} +div.pad1b{ + padding: 0 0 1px 0; +} +div.pad2b{ + padding: 0 0 2px 0; +} +div.pad2{ + padding: 2px 2px 2px 2px; +} +div.pad5{ + padding: 5px 5px 5px 5px; +} +div.pad0-5{ + padding: 0 5px 0 5px; +} +td.pad2{ + padding: 2px 2px 2px 2px; +} +div.pad3-5{ + padding: 3px 5px 3px 5px; +} +div.lett_from{ + padding: 3px 11px 3px 0; +} +div.pad11{ + padding: 11px 11px 11px 11px; +} +div.pad5-11{ + padding: 5px 11px 5px 11px; +} +div.pad10b{ + padding: 0 0 10px 0; +} +div.pad20b{ + padding: 0 0 20px 0; +} +div.mar0-7{ + margin: 0px 7px 0px 7px; +} +div.mar0-11{ + margin: 0px 11px 0px 11px; +} +div.mar0{ + margin: 0px 0px 0px 0px; +} +div.mar5 { + margin: 5px 5px 5px 5px; +} +div.yaritori{ + margin: 0 11px 0 11px; + background-color:#ffffff; + border-style:solid; + border-width:0 2px 0 2px; + border-color:#92d300; +} +div.gr-tit{ + margin: 0 11px 0 11px; + background-color:#92d300; + color:#ffffff; +} +div.kadomaru{ + margin: 0 11px 0 11px; +} +div.lett-note{ + margin: 5px 11px 5px 11px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#92d300; + background-color:#ffffff; +} +div.pk{ + background-color:#ff3ab7; +} +div.blue{ + background-color:#3acfff; +} +div.gray{ + background-color:#cccccc; +} +div.gray_box{ + padding:5px; + border-style:solid; + border-width:1px; + border-color:#cccccc; + background-color:#ffffff; +} +div.vio-intro { + width:240px; + padding:7px 0 7px 7px; +} +div.vio-memo { + padding:0 0 10px 15px; +} +div.vio-text { + padding:0 0 10px 15px; +} +div.vio-note { + color:#999999; + padding:0 0 10px 15px; +} +div.vio-submit { + padding:10px 0 10px 0; +} +div.info-head { + padding:5px 7px 5px 7px; + border-style:solid; + border-width:0 0 1px 0; + border-color:#CCCCCC; + background-color:#eeeeee; +} +div.noborder{ + border-style:solid; + border-color:#CCCCCC; + border-width:0 0 0 0; +} +div.info-body { + padding:7px; +} +div.info-msg { + margin:0 0 7px 0; +} +div.news-head { + padding:5px 7px 5px 7px; + border-style:solid; + border-width:0 0 1px 0; + border-color:#CCCCCC; + background-color:#eeeeee; +} +div.news-body { + padding:7px; +} +div.news-h1 { + margin:7px 7px 0 7px; + padding:7px; + border-style:solid; + border-width:3px; + border-color:#91C900; +} +div.news-h2 { + padding:0 0 3px 7px; + border-style:solid; + border-width:0 0 1px 4px; + border-color:#91C900; +} +img.news-memo { + margin:0 10px 5px 0; + border-style:solid; + border-width:1px; + border-color:#CCCCCC; +} +div.info-h2 { + padding:0 0 3px 7px; + border-style:solid; + border-width:0 0 1px 4px; + border-color:#91C900; +} +div.num-box{ + background-color:#cccccc; + margin:0 30px; +} +td.tit-star{ + background-color:#e9f8c3; +} +img.help{ + border-style:solid; + border-width:2px; + border-color:#ffffff; +} +div.list{ + padding:0 0 5px 0; +} +div.star-det{ + padding:5px 0 5px 0; +} +img.sc{ + border-style:solid; + border-width:1px; + border-color:#cccccc; +} +td.border { + background-color: #82c300; +} +div.border { + border-style: solid; + border-width: 0 0 5px 0; + border-color: #82c300; +} +div.border-c { + border-style: solid; + border-width: 0 0 2px 0; + border-color: #82c300; +} +td.space { + background-color: #82c300; + color: #82c300; +} +td.tabon { + color: #82c300; +} +td.taboff { + background-color: #82c300; + color: #ffffff; +} +td.tabpager { + background-color: #82c300; + color: #ffffff; +} +div.on { + padding: 1px 6px 3px 6px; +} +span.border { + color: #b2db59; +} +span.border-gray { + color: #cccccc; +} +a.taboff { + color: #ffffff; +} +div.comme { + padding: 3px 3px 3px 12px; + color: #b2db59; +} +span.text { + color: #333333; +} +td.gray { + background-color: #f2f2f2; +} +div.message { + width:226px; + margin:7px 7px 2px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#BCCDED; + background-color:#E5EEFE; +} +div.error-message { + width:226px; + margin:7px 7px 2px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#F5C8A7; + background-color:#FFEED8; + color:#9B2E2E; +} +div.ok-message { + width:226px; + margin:7px 7px 2px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#C5DABA; + background-color:#E3F9DB; + color:#517B29; +} +div.guide-h2 { + padding:0 0 3px 7px; + border-style:solid; + border-width:0 0 1px 4px; + border-color:#66ccff; +} +div.guide-h3 { + padding:3px 3px 3px 3px; + background-color:#66ccff; + color: #ffffff; +} +div.select-t { + color: #66ccff; + padding: 0 0 3px 0; +} +div.select-b { + padding:5px 3px 5px 3px; + border-style: solid; + border-width: 1px 1px 1px 1px; + border-color: #99ccff; + background-color: #ffffff; +} +span.border-s { + color: #99ccff; +} +span.color { + color: #ffffff; +} +div.credit { + padding:5px 7px 5px 7px; + background-color:#eeeeee; +} +div.rule-tit { + background-color: #82c300; + color: #ffffff; + padding: 3px 3px 3px 3px; +} +div.rule-ind { + margin: 0 0 0 10px; +} +div.hr-prof { + border-style:solid; + border-width:0 0 3px 0; + border-color:transparent; +} +table.send { + margin: 5px 0 0 0 ; +} +div.icon { + border-style: solid; + border-width: 1px 1px 1px 1px; + border-color: #cccccc; +} +td.username { + height:20px; +} +td.mystatus { +} +div.prev { + padding: 1px 0 5px 0; +} +div.next { + padding: 5px 0 1px 0; +} +div.prof-det{ + padding: 0 5px 0 5px; +} +div.catch { + margin: 5px 5px 5px 5px; + padding: 5px 5px 5px 5px; + border-style: solid; + border-width: 2px 2px 2px 2px; + border-color: #ffffff; +} +a.still { + color: #ffffff; +} +div.head { + margin: 0px 0 0 0; +} +div.send { + margin: 0px 10px 3px 11px; +} +div.link { + margin: 0px 10px 0 11px; +} +img.button { + margin: 5px 0 0 0; +} +td.theme { + margin: 0 0 0 3px; +} +div.trial-h1 { + margin: 5px 0 5px 0; + padding:3px; + border-style:solid; + border-width:3px; + border-color:#91C900; +} +div.mar-b10{ +margin:0 0 10px 0; +} +div.cam1{ + margin:7px 7px 0 7px; + padding:7px; + background-color: #ff2828; + color: #ffffff; +} +div.cam2{ + margin:0 7px 0 7px; + padding:7px; + border-style:solid; + border-width: 2px 2px 2px 2px; + border-color: #ff2828; +} +table.xmas2009{ + margin: 0 10px 0 11px; + background-color: #c70606; + color: #ffffff; +} +div.yellow { + margin: 0 11px 0 11px; + background-color:#ffe800; +} +div.sttr_mes { + width:226px; + margin:7px 7px 0px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#92cb22; + background-color:#ffffff; +} +div.sttr_gacha { + width:226px; + margin:7px 7px 0px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#ea475c; + background-color:#ffffff; +} +div.compsp { + width:226px; + margin:7px 7px 0px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#ffe800; + background-color:#fff9b7; +} +div.sttr_war { + width:226px; + margin:7px 7px 0px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#F5C8A7; + background-color:#FFEED8; + color:#9B2E2E; +} +div.sttr_price { + background-color:#FF9933; + color: #ffffff; +} +div.sttr_item { + width:226px; + margin:0px 7px 0px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#92cb22; + background-color:#ffffff; +} +div.sttr_theme { + width:226px; + margin:0px 7px 3px 7px; + padding:5px; + border-style:solid; + border-width:0px 1px 1px 1px; + border-color:#92cb22; + background-color:#ffffff; +} +div.sttr_back { + width:226px; + margin:0px 7px 0px 7px; +} +div.gift_l { + margin: 0 11px 0 11px; + border-style:solid; + border-width:0 2px 0px 2px; + border-color:#ff5252; +} +div.gift_h { + margin: 0 11px 0 11px; + border-style:solid; + border-width:0 2px 0px 2px; + border-color:#ffd616; +} +img.shop_icon { + margin:0 1px 1px 0; +} +div.help_guide { + border-style:solid; + border-width:0 1px 1px 1px; + border-color:#ffac3e; +} +div.guide-head { + color: #ffffff; + background-color: #5abcea; +} +div.guide-title { + color: #ffffff; + background-color: #5abcea; +} +div.guide-main { + border-style:solid; + border-width:0 1px 1px 1px; + border-color:#5abcea; +} +div.green-title { + color: #ffffff; + background-color: #91C900; +} +div.green-main { + border-style:solid; + border-width:0 1px 1px 1px; + border-color:#91C900; +} +table.method{ + border-style:solid; + border-width:1px; + border-color:#cccccc; +} +div.search { + background-color: #eeeeee; + border-style: solid; + border-width: 1px; + border-color:#cccccc; +} +td.keyboard { + background-color: #eeeeee; +} +table.key-tab{ + margin: 0 6px 0 6px; +} +div.keyout { + margin: 0 7px 0 7px; + border-style: solid; + border-width: 0 1px 1px 1px; + border-color:#cccccc; +} +div.on-ja { + border-style: solid; + border-width: 1px 1px 0 1px; + border-color:#cccccc; +} +div.off-en { + border-style: solid; + border-width: 1px 1px 0 0; + border-color:#cccccc; +} +div.off-aa { + border-style: solid; + border-width: 1px 1px 0 0; + border-color:#cccccc; +} +table.camp-detail { + border-style: solid; + border-width: 1px; + border-color:#cccccc; +} +table.center { + margin: 0 15px 0 16px; +} +div.codeborder { + border-style: solid; + border-width:0 1px 1px 1px; + border-color:#82c300; +} +div.codeprev { + border-style: solid; + border-width:1px; + border-color:#82c300; +} +td.code { + background-color:#ffffff; +} +table.codetab { + border-style: solid; + border-width: 1px 1px 0 1px; + border-color:#82c300; +} +div.codetab { + border-style: solid; + border-width: 0 1px 1px 1px; + border-color:#82c300; + background-color:#ffffff; +} +div.c_tab2 { + border-style: solid; + border-width: 1px 1px 1px 1px; + border-color:#82c300; + background-color:#ffffff; +} +div.topic { + border-style:solid; + border-width:1px; + border-color:#fff273; + background-color:#fff8b2; +} +div.icon-green { + border-style: solid; + border-width: 1px 1px 1px 1px; + border-color: #82c300; +} +div.p-block { + border-style:solid; + border-width:1px; + border-color:#000000; + color:#000000; + background-color:#ffffff; +} +div.clstar-help{ + background-color:#ffffff; + color:#82c300; +} +div.ugo4th{ + border-style:solid; + border-width:1px; + border-color:#c4260f; + background-color:#d52515; +} diff --git a/hatenadir/css/ds/common.css b/hatenadir/css/ds/common.css new file mode 100644 index 0000000..04949bb --- /dev/null +++ b/hatenadir/css/ds/common.css @@ -0,0 +1,808 @@ +@charset "UTF-8"; +span.url{ + text-decoration:underline; +} +span.comptxt { + color:#ff8a00; +} +span.star0 { + color:#fdc556; +} +span.star1 { + color:#138200; +} +span.star2 { + color:#ff455c; +} +span.star3 { + color:#00b0f9; +} +span.star4 { + color:#a411b9; +} +span.star0c { + color:#fdc556; +} +span.star1c { + color:#138200; +} +span.star2c { + color:#ff455c; +} +span.star3c { + color:#00b0f9; +} +span.star4c { + color:#a411b9; +} +span.orange{ + color: #ff8d45; +} +span.green { + color: #82c300; +} +span.gray{ + color: #666666; +} +span.white { + color:#FFFFFF; +} +html { + margin:0; + padding:0; +} +body { + color:#333333; + margin:0; + padding:0; +} +div.title { + margin:0 7px 0 7px; + padding:7px 5px 7px 5px; + border-style:solid; + border-width:0 0 1px 0; + border-color:#CCCCCC; +} +div.description { + margin:0 7px 0 7px; + padding:7px 5px 7px 5px; + border-style:solid; + border-width:0 0 1px 0; + border-color:#CCCCCC; +} +div.hr { + border-style:solid; + border-width:0 0 1px 0; + border-color:#CCCCCC; +} +div.hr-tab { + border-style:solid; + border-width:0 0 1px 0; + border-color:#82c300; +} +span.timestamp { + color:#999999; +} +a.timestamp { + color:#999999; +} +a.violation { + color:#333333; +} +div.box { + padding:7px; + border-style:solid; + border-width:1px; + border-color:#CCCCCC; + background-color:#FFFFFF; +} +div.more { + margin:7px 0 0 0; +} +div.pager { + padding:5px; +} +div.num { + margin:5px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#CCCCCC; + background-color:#FFFFFF; +} +span.new { + color: #cc0000; +} +span.price { + color: #92cb22; +} +span.date { + color: #cccccc; +} +table.detail{ + margin:0 7px 0 7px; +} +table.fullsize { + margin:0; +} +div.item-term { + padding:3px 0 3px 5px; +} +div.item-value { + padding:3px 5px 3px 0; +} +div.item-text { + padding:3px 5px 3px 15px; +} +div.item-tcom { + padding: 3px 5px 0 5px; +} +div.item-com { + padding: 0 5px 0 5px; +} +div.td-body { + padding:3px 5px 3px 5px; +} +div.comlist { + background-color:#FFFFFF; +} +div.comment-text { + padding:3px 5px 5px 15px; +} +div.comment-memo { + padding:0 0 0 15px; +} +div.staff { + color:#FFFFFF; + background-color:#1a1a1a; +} +a.staffnam { + color:#FFFFFF; +} +div.notice { + width:226px; + margin:7px 7px 3px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#BCCDED; + background-color:#E5EEFE; +} +div.colorstar { + margin:7px 0 7px 0; +} +div.code-box{ + width:226px; + margin:7px 0 0 0; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#BCCDED; + background-color:#E5EEFE; +} +div.code-data{ + margin:7px 0 7px 0; +} +div.mail-reply { + width:226px; + margin:7px 7px 2px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#BCCDED; + background-color:#E5EEFE; +} +div.lett_txt { + margin: 0 11px 0 11px; + border-style:solid; + border-width:0 2px 0 2px; + border-color:#92d300; + background-color:#ffffff; +} +div.lett_to { + margin: 0 11px 0 11px; + background-color:#92d300; + color:#ffffff; + padding: 1px 0 6px 5px; +} +div.pad0{ + padding: 0 0 0 0; +} +div.pad5t{ + padding: 5px 0 0 0; +} +div.pad5b{ + padding: 0 0 5px 0; +} +div.pad1b{ + padding: 0 0 1px 0; +} +div.pad2b{ + padding: 0 0 2px 0; +} +div.pad2{ + padding: 2px 2px 2px 2px; +} +div.pad5{ + padding: 5px 5px 5px 5px; +} +div.pad0-5{ + padding: 0 5px 0 5px; +} +td.pad2{ + padding: 2px 2px 2px 2px; +} +div.pad3-5{ + padding: 3px 5px 3px 5px; +} +div.lett_from{ + padding: 3px 11px 3px 0; +} +div.pad11{ + padding: 11px 11px 11px 11px; +} +div.pad5-11{ + padding: 5px 11px 5px 11px; +} +div.pad10b{ + padding: 0 0 10px 0; +} +div.pad20b{ + padding: 0 0 20px 0; +} +div.mar0-7{ + margin: 0px 7px 0px 7px; +} +div.mar0-11{ + margin: 0px 11px 0px 11px; +} +div.mar0{ + margin: 0px 0px 0px 0px; +} +div.mar5 { + margin: 5px 5px 5px 5px; +} +div.yaritori{ + margin: 0 11px 0 11px; + background-color:#ffffff; + border-style:solid; + border-width:0 2px 0 2px; + border-color:#92d300; +} +div.gr-tit{ + margin: 0 11px 0 11px; + background-color:#92d300; + color:#ffffff; +} +div.kadomaru{ + margin: 0 11px 0 11px; +} +div.lett-note{ + margin: 5px 11px 5px 11px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#92d300; + background-color:#ffffff; +} +div.pk{ + background-color:#ff3ab7; +} +div.blue{ + background-color:#3acfff; +} +div.gray{ + background-color:#cccccc; +} +div.gray_box{ + padding:5px; + border-style:solid; + border-width:1px; + border-color:#cccccc; + background-color:#ffffff; +} +div.vio-intro { + width:240px; + padding:7px 0 7px 7px; +} +div.vio-memo { + padding:0 0 10px 15px; +} +div.vio-text { + padding:0 0 10px 15px; +} +div.vio-note { + color:#999999; + padding:0 0 10px 15px; +} +div.vio-submit { + padding:10px 0 10px 0; +} +div.info-head { + padding:5px 7px 5px 7px; + border-style:solid; + border-width:0 0 1px 0; + border-color:#CCCCCC; + background-color:#eeeeee; +} +div.noborder{ + border-style:solid; + border-color:#CCCCCC; + border-width:0 0 0 0; +} +div.info-body { + padding:7px; +} +div.info-msg { + margin:0 0 7px 0; +} +div.news-head { + padding:5px 7px 5px 7px; + border-style:solid; + border-width:0 0 1px 0; + border-color:#CCCCCC; + background-color:#eeeeee; +} +div.news-body { + padding:7px; +} +div.news-h1 { + margin:7px 7px 0 7px; + padding:7px; + border-style:solid; + border-width:3px; + border-color:#91C900; +} +div.news-h2 { + padding:0 0 3px 7px; + border-style:solid; + border-width:0 0 1px 4px; + border-color:#91C900; +} +img.news-memo { + margin:0 10px 5px 0; + border-style:solid; + border-width:1px; + border-color:#CCCCCC; +} +div.info-h2 { + padding:0 0 3px 7px; + border-style:solid; + border-width:0 0 1px 4px; + border-color:#91C900; +} +div.num-box{ + background-color:#cccccc; + margin:0 30px; +} +td.tit-star{ + background-color:#e9f8c3; +} +img.help{ + border-style:solid; + border-width:2px; + border-color:#ffffff; +} +div.list{ + padding:0 0 5px 0; +} +div.star-det{ + padding:5px 0 5px 0; +} +img.sc{ + border-style:solid; + border-width:1px; + border-color:#cccccc; +} +td.border { + background-color: #82c300; +} +div.border { + border-style: solid; + border-width: 0 0 5px 0; + border-color: #82c300; +} +div.border-c { + border-style: solid; + border-width: 0 0 2px 0; + border-color: #82c300; +} +td.space { + background-color: #82c300; + color: #82c300; +} +td.tabon { + color: #82c300; +} +td.taboff { + background-color: #82c300; + color: #ffffff; +} +td.tabpager { + background-color: #82c300; + color: #ffffff; +} +div.on { + padding: 1px 6px 3px 6px; +} +span.border { + color: #b2db59; +} +span.border-gray { + color: #cccccc; +} +a.taboff { + color: #ffffff; +} +div.comme { + padding: 3px 3px 3px 12px; + color: #b2db59; +} +span.text { + color: #333333; +} +td.gray { + background-color: #f2f2f2; +} +div.message { + width:226px; + margin:7px 7px 2px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#BCCDED; + background-color:#E5EEFE; +} +div.error-message { + width:226px; + margin:7px 7px 2px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#F5C8A7; + background-color:#FFEED8; + color:#9B2E2E; +} +div.ok-message { + width:226px; + margin:7px 7px 2px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#C5DABA; + background-color:#E3F9DB; + color:#517B29; +} +div.guide-h2 { + padding:0 0 3px 7px; + border-style:solid; + border-width:0 0 1px 4px; + border-color:#66ccff; +} +div.guide-h3 { + padding:3px 3px 3px 3px; + background-color:#66ccff; + color: #ffffff; +} +div.select-t { + color: #66ccff; + padding: 0 0 3px 0; +} +div.select-b { + padding:5px 3px 5px 3px; + border-style: solid; + border-width: 1px 1px 1px 1px; + border-color: #99ccff; + background-color: #ffffff; +} +span.border-s { + color: #99ccff; +} +span.color { + color: #ffffff; +} +div.credit { + padding:5px 7px 5px 7px; + background-color:#eeeeee; +} +div.rule-tit { + background-color: #82c300; + color: #ffffff; + padding: 3px 3px 3px 3px; +} +div.rule-ind { + margin: 0 0 0 10px; +} +div.hr-prof { + border-style:solid; + border-width:0 0 3px 0; + border-color:transparent; +} +table.send { + margin: 5px 0 0 0 ; +} +div.icon { + border-style: solid; + border-width: 1px 1px 1px 1px; + border-color: #cccccc; +} +td.username { + height:20px; +} +td.mystatus { +} +div.prev { + padding: 1px 0 5px 0; +} +div.next { + padding: 5px 0 1px 0; +} +div.prof-det{ + padding: 0 5px 0 5px; +} +div.catch { + margin: 5px 5px 5px 5px; + padding: 5px 5px 5px 5px; + border-style: solid; + border-width: 2px 2px 2px 2px; + border-color: #ffffff; +} +a.still { + color: #ffffff; +} +div.head { + margin: 0px 0 0 0; +} +div.send { + margin: 0px 10px 3px 11px; +} +div.link { + margin: 0px 10px 0 11px; +} +img.button { + margin: 5px 0 0 0; +} +td.theme { + margin: 0 0 0 3px; +} +div.trial-h1 { + margin: 5px 0 5px 0; + padding:3px; + border-style:solid; + border-width:3px; + border-color:#91C900; +} +div.mar-b10{ +margin:0 0 10px 0; +} +div.cam1{ + margin:7px 7px 0 7px; + padding:7px; + background-color: #ff2828; + color: #ffffff; +} +div.cam2{ + margin:0 7px 0 7px; + padding:7px; + border-style:solid; + border-width: 2px 2px 2px 2px; + border-color: #ff2828; +} +table.xmas2009{ + margin: 0 10px 0 11px; + background-color: #c70606; + color: #ffffff; +} +div.yellow { + margin: 0 11px 0 11px; + background-color:#ffe800; +} +div.sttr_mes { + width:226px; + margin:7px 7px 0px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#92cb22; + background-color:#ffffff; +} +div.sttr_gacha { + width:226px; + margin:7px 7px 0px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#ea475c; + background-color:#ffffff; +} +div.compsp { + width:226px; + margin:7px 7px 0px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#ffe800; + background-color:#fff9b7; +} +div.sttr_war { + width:226px; + margin:7px 7px 0px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#F5C8A7; + background-color:#FFEED8; + color:#9B2E2E; +} +div.sttr_price { + background-color:#FF9933; + color: #ffffff; +} +div.sttr_item { + width:226px; + margin:0px 7px 0px 7px; + padding:5px; + border-style:solid; + border-width:1px; + border-color:#92cb22; + background-color:#ffffff; +} +div.sttr_theme { + width:226px; + margin:0px 7px 3px 7px; + padding:5px; + border-style:solid; + border-width:0px 1px 1px 1px; + border-color:#92cb22; + background-color:#ffffff; +} +div.sttr_back { + width:226px; + margin:0px 7px 0px 7px; +} +div.gift_l { + margin: 0 11px 0 11px; + border-style:solid; + border-width:0 2px 0px 2px; + border-color:#ff5252; +} +div.gift_h { + margin: 0 11px 0 11px; + border-style:solid; + border-width:0 2px 0px 2px; + border-color:#ffd616; +} +img.shop_icon { + margin:0 1px 1px 0; +} +div.help_guide { + border-style:solid; + border-width:0 1px 1px 1px; + border-color:#ffac3e; +} +div.guide-head { + color: #ffffff; + background-color: #5abcea; +} +div.guide-title { + color: #ffffff; + background-color: #5abcea; +} +div.guide-main { + border-style:solid; + border-width:0 1px 1px 1px; + border-color:#5abcea; +} +div.green-title { + color: #ffffff; + background-color: #91C900; +} +div.green-main { + border-style:solid; + border-width:0 1px 1px 1px; + border-color:#91C900; +} +table.method{ + border-style:solid; + border-width:1px; + border-color:#cccccc; +} +div.search { + background-color: #eeeeee; + border-style: solid; + border-width: 1px; + border-color:#cccccc; +} +td.keyboard { + background-color: #eeeeee; +} +table.key-tab{ + margin: 0 6px 0 6px; +} +div.keyout { + margin: 0 7px 0 7px; + border-style: solid; + border-width: 0 1px 1px 1px; + border-color:#cccccc; +} +div.on-ja { + border-style: solid; + border-width: 1px 1px 0 1px; + border-color:#cccccc; +} +div.off-en { + border-style: solid; + border-width: 1px 1px 0 0; + border-color:#cccccc; +} +div.off-aa { + border-style: solid; + border-width: 1px 1px 0 0; + border-color:#cccccc; +} +table.camp-detail { + border-style: solid; + border-width: 1px; + border-color:#cccccc; +} +table.center { + margin: 0 15px 0 16px; +} +div.codeborder { + border-style: solid; + border-width:0 1px 1px 1px; + border-color:#82c300; +} +div.codeprev { + border-style: solid; + border-width:1px; + border-color:#82c300; +} +td.code { + background-color:#ffffff; +} +table.codetab { + border-style: solid; + border-width: 1px 1px 0 1px; + border-color:#82c300; +} +div.codetab { + border-style: solid; + border-width: 0 1px 1px 1px; + border-color:#82c300; + background-color:#ffffff; +} +div.c_tab2 { + border-style: solid; + border-width: 1px 1px 1px 1px; + border-color:#82c300; + background-color:#ffffff; +} +div.topic { + border-style:solid; + border-width:1px; + border-color:#fff273; + background-color:#fff8b2; +} +div.icon-green { + border-style: solid; + border-width: 1px 1px 1px 1px; + border-color: #82c300; +} +div.p-block { + border-style:solid; + border-width:1px; + border-color:#000000; + color:#000000; + background-color:#ffffff; +} +div.clstar-help{ + background-color:#ffffff; + color:#82c300; +} +div.ugo4th{ + border-style:solid; + border-width:1px; + border-color:#c4260f; + background-color:#d52515; +} \ No newline at end of file diff --git a/hatenadir/ds/v2-xx/__ntfts/News.ntft b/hatenadir/ds/v2-xx/__ntfts/News.ntft new file mode 100644 index 0000000..8dadbcf --- /dev/null +++ b/hatenadir/ds/v2-xx/__ntfts/News.ntft @@ -0,0 +1 @@ +’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’ςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Ó’Ó’Óςς’Ó’Óςς’Ó’Ó’Ó’Ó’Ó’Óςςςςςςςς’Ó’Ó’Óςςςς’Ó’Ó’Ó’ÓĻ‚’Óςςςς’ÓĻ‚’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Ó’ÓĻ‚’Óςςςς’ÓĻ‚’Ó’Ó’Ó’Ó’Óςςςςςςςς’Ó’Ó’Óςςςς’Ó’Ó’Ó’Óςςςςςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Óςςςςςςςςςς’Ó’Ó’Ó’Óςςςςςςςς’Ó’Ó’Óςςςς’Ó’Ó’Óςς’Ó’Ó’Ó’Ó’Ó’Óςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Óςςς’Ó’Ó’Ó’Ó’Ó’Óςςς’Ó’Ó’Óςςςςςςςς’Ó’Ó’Óςςςς’Ó’Óςςςς’Ó’Ó’Ó’Óςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Óςςςς’Ó’Óςςςς’Ó’Ó’Ó’Óςςςςςςςς’Ó’Ó’Óςςςς’Ó’Óςςςςςςςςςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Óςςςςςςςςςςςςςςς’Óςςςςςς’Ó’Ó’Óςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Óςςςςςς’Óςςςςςςςςςςςςςςς’Ó’Ó’Óςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Óςςςςςςςςς’Óςςςςςςςςςςςς’Ó’Ó’Óςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Óςςςςςςςςςςςςςςς’Óςςςςςς’Ó’Ó’Óςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςς’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Ó’Óςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςςς’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’ \ No newline at end of file diff --git a/hatenadir/ds/v2-xx/__ntfts/Special Room.ntft b/hatenadir/ds/v2-xx/__ntfts/Special Room.ntft new file mode 100644 index 0000000..cbf8957 --- /dev/null +++ b/hatenadir/ds/v2-xx/__ntfts/Special Room.ntft @@ -0,0 +1 @@ +’’’’’’’’’’’’’’’»ćšŪÜė’’’’’’’’’’’’’’’’’’’’’’’’ŻļyÓ«®Š®Š®Š®Š®Š®ŠŠ–5»»ß’’’’’’’’’’’’’’’’’’ŻļV殊®Š®Š®Š®Š®Š®Š®Š®Š®Š®Š®ŠńžšŪ’’’’’’’’’’’’’’’xĻ®Š®Š®Š®Š®Š®ŠŠŽņšŸń’ŠŽ®Š®Š®Š®Š®Šņ¢Üė’’’’’’’’’’’’5·®Š®Š®Š®Š5£š·žĒ’˒˒˒˒˼æxÆŃ’®Š®Š®ŠÆŠšß’’’’’’’’’’Æ®Š®Š®Šņš»»’Ė’Ė’Ė’Ė’ĖW«®ŠŻĆ’Ė’Ė’Ėy³®Š®Š®Š®Ššß’’’’’’’’VîЮЮŠV§’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«®Šń–š·’Ė’Ė’Ė¼æŠ’®Š®Š®Š¼ė’’’’’’šŪ®Š®Š®ŠW«’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«®Š®Š®ŠÆŽ6§ŻĆ’ĖŻĆŠ’®Š®ŠŃš’’’’’’Š–®Š®Š4£’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«ÆŽ®Š®Š®Š®ŠÆŽy³’ĖÜæ®Š®Š®ŠyÓ’’’’™×®Š®ŠŠ’ŽĒ’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«ņš¼æ5£®Š®Š®ŠÆŽŻĆ’˚·®Š®ŠĻ’’’’’ņ¢®Š®Šš·’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«ņš’Ė’ĖžĒW«®Š®Š»»’Ė’Ėņ–®Š®Šš×’’Ü뮊®ŠŠŽ’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«ņš’Ė’Ė’Ė’Ė5£®ŠŻĆ’˒˚·®Š®Š«’’WîЮŠ5£’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«ņš’Ė’Ė’ĖžĒŠ’6§’Ė’Ė’ĖžĒ®Š®Š®Š’’³®Š®Šš·’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«ņš’Ė’Ė’ĖŻĆń–’Ė’Ė’Ė’Ė’Ėņ–®Š®Š¼ē’ņ¢®Š®Š¼æ’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«ņš’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė4Ÿ®Š®Š™×’њ®Š®ŠŻĆ’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«ņš’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė5£®Š®ŠxĻ’њ®Š®ŠŻĆ’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«ņš’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė5£®Š®ŠxĻ’ņ¦®Š®Š»»’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«ņš’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖŸ®Š®Šš×’5»®Š®ŠxÆ’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«ņš’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ėń’®Š®ŠÜė’yӮЮŠ5£’Ė’Ė’Ė’Ė’ĖŻĆxÆŸņš6§»»W«ņš’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖŻĆ®Š®ŠĻ’’’Żļ®Š®ŠÆŠŻĆ’Ė’Ė’Ė6§®Š®Š®Š®Š®Š®ŠŠ’ņš’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖW«®Š®Š5·’’’4·®Š®ŠV§’Ė’Ė¼æ®Š®Š®Š®Š®Š®Š®Š®Šņš’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖŻĆÆŠ®Š®Š»ć’’’»ć®Š®Š®Š¼æ’Ė»»®Š®Š®Š®Š®Š®Š®Š®Šņš’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖV§®Š®Šņ¦’’’’’³®Š®ŠŠ’ŽĆ’Ėń–®Š®Š®Š®Š®Š®ŠÆŽ¼æ’Ė’Ė’Ė’Ė’Ė’Ė’Ė™³®Š®Š®Š»ć’’’’’¼ė®Š®Š®Š›ŽĆ’Ėy³W«ņšņš6§y³žĒ’Ė’Ė’Ė’Ė’Ė’Ė’Ė»»®Š®Š®Š4³’’’’’’’šß®Š®Š®Šń’Żæ’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’ĖxÆ®Š®Š®Šńž’’’’’’’’’xϮЮЮЮŠw«žĒ’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė’Ė»»Ÿ®Š®Š®ŠŠ’Żļ’’’’’’’’’’š×®Š®Š®Š®ŠŠŽ5£š·ŻĆŽĆžĒŽĆ»»xÆ›®Š®Š®Š®Šņ¦’’’’’’’’’’’’’¼ė«®Š®Š®Š®Š®Š®Š®Š®Š®Š®Š®Š®Š®Š®Š®ŠWĒ’’’’’’’’’’’’’’’’»ßÆ®Š®Š®Š®Š®Š®Š®Š®Š®Š®ŠÆŽVæÜė’’’’’’’’’’’’’’’’’’’ÜėxĻÆŃšŠ’Ļ’Š’ņ¢5»»ß’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’ \ No newline at end of file diff --git a/hatenadir/ds/v2-xx/en/confirm/delete.txt b/hatenadir/ds/v2-xx/en/confirm/delete.txt new file mode 100644 index 0000000..5c00cb2 Binary files /dev/null and b/hatenadir/ds/v2-xx/en/confirm/delete.txt differ diff --git a/hatenadir/ds/v2-xx/en/confirm/download.txt b/hatenadir/ds/v2-xx/en/confirm/download.txt new file mode 100644 index 0000000..75a7208 Binary files /dev/null and b/hatenadir/ds/v2-xx/en/confirm/download.txt differ diff --git a/hatenadir/ds/v2-xx/en/confirm/upload.txt b/hatenadir/ds/v2-xx/en/confirm/upload.txt new file mode 100644 index 0000000..6344c8e Binary files /dev/null and b/hatenadir/ds/v2-xx/en/confirm/upload.txt differ diff --git a/hatenadir/ds/v2-xx/en/confirm/upload_bak.txt b/hatenadir/ds/v2-xx/en/confirm/upload_bak.txt new file mode 100644 index 0000000..d0768fb Binary files /dev/null and b/hatenadir/ds/v2-xx/en/confirm/upload_bak.txt differ diff --git a/hatenadir/ds/v2-xx/en/eula.txt b/hatenadir/ds/v2-xx/en/eula.txt new file mode 100644 index 0000000..f09fdbb Binary files /dev/null and b/hatenadir/ds/v2-xx/en/eula.txt differ diff --git a/hatenadir/ds/v2-xx/eula_list.tsv b/hatenadir/ds/v2-xx/eula_list.tsv new file mode 100644 index 0000000..63798b1 --- /dev/null +++ b/hatenadir/ds/v2-xx/eula_list.tsv @@ -0,0 +1 @@ +RQBuAGcAbABpAHMAaAA= en diff --git a/hatenadir/ds/v2-xx/frontpage/hotmovies.ugo.py b/hatenadir/ds/v2-xx/frontpage/hotmovies.ugo.py new file mode 100644 index 0000000..5e0e891 --- /dev/null +++ b/hatenadir/ds/v2-xx/frontpage/hotmovies.ugo.py @@ -0,0 +1,94 @@ +from twisted.web import resource +from twisted.internet import reactor +import time + +from hatena import Database, Log, NotFound +from Hatenatools import UGO + +#makes a flipnote list of the most seen ones among the recently uploaded +class PyResource(resource.Resource): + isLeaf = True + def __init__(self): + resource.Resource.__init__(self) + + self.pages = []#10 first pages + self.newestview = None + self.Update() + def render(self, request): + page = int(request.args["page"][0]) if "page" in request.args else 1 + + if len(self.pages) >= page: + path = "/".join(request.path.split("/")[3:]) + Log(request, "%s page %i" % (path, page)) + + request.responseHeaders.setRawHeaders('content-type', ['text/plain']) + return self.pages[page-1] + else: + return NotFound.render(request) + #=== + def Update(self):#called every 15 minutes + reactor.callLater(60*10, self.Update) + + if self.newestview <> Database.Views: + self.newestview = Database.Views + reactor.callInThread(self.UpdateThreaded, Database.Newest) + def UpdateThreaded(self, flipnotes):#run in an another thread + #sort the flipnotes by viewcount, affected by amount of stars + def sort((i, (ID, flip))): + views, stars = Database.GetFlipnote(ID, flip)[1:3] + return int(views)*60 + int(stars)/4 - i + flipnotes = map(lambda x: x[1], sorted(enumerate(flipnotes), key=sort)[::-1])[:500] + + #create pages: + pages = [] + pagecount = (len(flipnotes)-1)/50 + 1 + if pagecount > 10: pagecount = 10#temp? + flipcount = len(flipnotes) if len(flipnotes) < 500 else 500 + + for i in xrange(pagecount): + pages.append(self.MakePage(flipnotes[i*50:i*50+50], i+1, i<pagecount-1, flipcount)) + + if self.pages:#not on startup + print time.strftime("[%H:%M:%S] Updated hotmovies.ugo") + self.pages = pages + def MakePage(self, flipnotes, page, next, count): + ugo = UGO() + ugo.Loaded = True + ugo.Items = [] + + #meta + ugo.Items.append(("layout", (2, 1))) + ugo.Items.append(("topscreen text", ["Popular Flipnotes", "Flipnotes", str(count), "", "The most popular new Flipnotes."], 0)) + + #categories + ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/hotmovies.uls", "Most Popular", True)) + ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/likedmovies.uls", "Most Liked", False)) + #ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/recommended.uls", "Recommended", False)) + ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/newmovies.uls", "New Flipnotes", False)) + #ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/following.uls", "New Favourites", False)) + + #the "post flipnote" button + ugo.Items.append(("unknown", ("3", "http://flipnote.hatena.com/ds/v2-xx/help/post_howto.htm", "UABvAHMAdAAgAEYAbABpAHAAbgBvAHQAZQA="))) + + #previous page + if page > 1: + ugo.Items.append(("button", 115, "Previous", "http://flipnote.hatena.com/ds/v2-xx/frontpage/hotmovies.uls?page=%i" % (page-1), ("", ""), None)) + + #Flipnotes + for creatorid, filename in flipnotes:#[i*50 : i*50+50]: + stars = str(Database.GetFlipnote(creatorid, filename)[2]) + + f = open(Database.FlipnotePath(creatorid, filename+".ppm"), "rb") + ugo.Items.append(("button", 3, "", "http://flipnote.hatena.com/ds/v2-xx/movie/%s/%s.ppm" % (creatorid, filename), (stars, "765", "573", "0"), ("bleh", f.read(0x6a0)))) + f.close() + + #next page + if next: + ugo.Items.append(("button", 115, "Next", "http://flipnote.hatena.com/ds/v2-xx/frontpage/hotmovies.uls?page=%i" % (page+1), ("", ""), None)) + + return ugo.Pack() + + + + + diff --git a/hatenadir/ds/v2-xx/frontpage/likedmovies.ugo.py b/hatenadir/ds/v2-xx/frontpage/likedmovies.ugo.py new file mode 100644 index 0000000..3544ac2 --- /dev/null +++ b/hatenadir/ds/v2-xx/frontpage/likedmovies.ugo.py @@ -0,0 +1,94 @@ +from twisted.web import resource +from twisted.internet import reactor +import time + +from hatena import Database, Log, NotFound +from Hatenatools import UGO + +#makes a flipnote list of the most seen ones among the recently uploaded +class PyResource(resource.Resource): + isLeaf = True + def __init__(self): + resource.Resource.__init__(self) + + self.pages = []#10 first pages + self.newestview = None + self.Update() + def render(self, request): + page = int(request.args["page"][0]) if "page" in request.args else 1 + + if len(self.pages) >= page: + path = "/".join(request.path.split("/")[3:]) + Log(request, "%s page %i" % (path, page)) + + request.responseHeaders.setRawHeaders('content-type', ['text/plain']) + return self.pages[page-1] + else: + return NotFound.render(request) + #=== + def Update(self):#called every 15 minutes + reactor.callLater(60*10, self.Update) + + if self.newestview <> Database.Views: + self.newestview = Database.Views + reactor.callInThread(self.UpdateThreaded, Database.Newest) + def UpdateThreaded(self, flipnotes):#run in an another thread + #sort the flipnotes by viewcount, affected by amount of stars + def sort((i, (ID, flip))): + views, stars = Database.GetFlipnote(ID, flip)[1:3] + return int(stars)*110 + int(views)/10 - i + flipnotes = map(lambda x: x[1], sorted(enumerate(flipnotes), key=sort)[::-1])[:500] + + #create pages: + pages = [] + pagecount = (len(flipnotes)-1)/50 + 1 + if pagecount > 10: pagecount = 10#temp? + flipcount = len(flipnotes) if len(flipnotes) < 500 else 500 + + for i in xrange(pagecount): + pages.append(self.MakePage(flipnotes[i*50:i*50+50], i+1, i<pagecount-1, flipcount)) + + if self.pages:#not on startup + print time.strftime("[%H:%M:%S] Updated hotmovies.ugo") + self.pages = pages + def MakePage(self, flipnotes, page, next, count): + ugo = UGO() + ugo.Loaded = True + ugo.Items = [] + + #meta + ugo.Items.append(("layout", (2, 1))) + ugo.Items.append(("topscreen text", ["Liked Flipnotes", "Flipnotes", str(count), "", "The most liked new Flipnotes."], 0)) + + #categories + ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/hotmovies.uls", "Most Popular", False)) + ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/likedmovies.uls", "Most Liked", True)) + #ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/recommended.uls", "Recommended", False)) + ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/newmovies.uls", "New Flipnotes", False)) + #ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/following.uls", "New Favourites", False)) + + #the "post flipnote" button + ugo.Items.append(("unknown", ("3", "http://flipnote.hatena.com/ds/v2-xx/help/post_howto.htm", "UABvAHMAdAAgAEYAbABpAHAAbgBvAHQAZQA="))) + + #previous page + if page > 1: + ugo.Items.append(("button", 115, "Previous", "http://flipnote.hatena.com/ds/v2-xx/frontpage/likedmovies.uls?page=%i" % (page-1), ("", ""), None)) + + #Flipnotes + for creatorid, filename in flipnotes:#[i*50 : i*50+50]: + stars = str(Database.GetFlipnote(creatorid, filename)[2]) + + f = open(Database.FlipnotePath(creatorid, filename+".ppm"), "rb") + ugo.Items.append(("button", 3, "", "http://flipnote.hatena.com/ds/v2-xx/movie/%s/%s.ppm" % (creatorid, filename), (stars, "765", "573", "0"), ("bleh", f.read(0x6a0)))) + f.close() + + #next page + if next: + ugo.Items.append(("button", 115, "Next", "http://flipnote.hatena.com/ds/v2-xx/frontpage/likedmovies.uls?page=%i" % (page+1), ("", ""), None)) + + return ugo.Pack() + + + + + diff --git a/hatenadir/ds/v2-xx/frontpage/newmovies.ugo.py b/hatenadir/ds/v2-xx/frontpage/newmovies.ugo.py new file mode 100644 index 0000000..3e95eaa --- /dev/null +++ b/hatenadir/ds/v2-xx/frontpage/newmovies.ugo.py @@ -0,0 +1,91 @@ +from twisted.web import resource +from twisted.internet import reactor +import time + +from hatena import Database, Log, NotFound +from Hatenatools import UGO + +#makes a flipnote list of the newest flipnotes +class PyResource(resource.Resource): + isLeaf = True + def __init__(self): + resource.Resource.__init__(self) + + self.pages = []#10 first pages + self.newestflip = None + self.Update() + def render(self, request): + page = int(request.args["page"][0]) if "page" in request.args else 1 + + if len(self.pages) >= page: + path = "/".join(request.path.split("/")[3:]) + Log(request, "%s page %i" % (path, page)) + + request.responseHeaders.setRawHeaders('content-type', ['text/plain']) + return self.pages[page-1] + #elif (page-1)*50 < len(Database.Newest): + # pass + else: + return NotFound.render(request) + #=== + def Update(self):#called every minute + reactor.callLater(60, self.Update) + + if self.newestflip <> Database.Newest[0]: + self.newestflip = Database.Newest[0] + reactor.callInThread(self.UpdateThreaded, Database.Newest) + def UpdateThreaded(self, flipnotes):#run in an another thread + #create pages: + pages = [] + pagecount = (len(flipnotes)-1)/50 + 1 + if pagecount > 10: pagecount = 10#temp? + flipcount = len(flipnotes) + + for i in xrange(min((pagecount, 10))): + pages.append(self.MakePage(flipnotes[i*50:i*50+50], i+1, i<pagecount-1, flipcount)) + + if self.pages:#not on startup + print time.strftime("[%H:%M:%S] Updated newmovies.ugo") + self.pages = pages + def MakePage(self, flipnotes, page, next, count): + ugo = UGO() + ugo.Loaded = True + ugo.Items = [] + + #meta + ugo.Items.append(("layout", (2, 1))) + ugo.Items.append(("topscreen text", ["New Flipnotes", "Flipnotes", str(count), "", "The newest Flipnotes submitted."], 0)) + + #categories + ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/hotmovies.uls", "Most Popular", False)) + ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/likedmovies.uls", "Most Liked", False)) + #ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/recommended.uls", "Recommended", False)) + ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/newmovies.uls", "New Flipnotes", True)) + #ugo.Items.append(("category", "http://flipnote.hatena.com/ds/v2-xx/frontpage/following.uls", "New Favourites", False)) + + #the "post flipnote" button + ugo.Items.append(("unknown", ("3", "http://flipnote.hatena.com/ds/v2-xx/help/post_howto.htm", "UABvAHMAdAAgAEYAbABpAHAAbgBvAHQAZQA="))) + + #previous page + if page > 1: + ugo.Items.append(("button", 115, "Previous", "http://flipnote.hatena.com/ds/v2-xx/frontpage/newmovies.uls?page=%i" % (page-1), ("", ""), None)) + + #Flipnotes + for creatorid, filename in flipnotes:#[i*50 : i*50+50]: + stars = str(Database.GetFlipnote(creatorid, filename)[2]) + + f = open(Database.FlipnotePath(creatorid, filename+".ppm"), "rb") + ugo.Items.append(("button", 3, "", "http://flipnote.hatena.com/ds/v2-xx/movie/%s/%s.ppm" % (creatorid, filename), (stars, "765", "573", "0"), ("bleh", f.read(0x6a0)))) + f.close() + + #next page + if next: + ugo.Items.append(("button", 115, "Next", "http://flipnote.hatena.com/ds/v2-xx/frontpage/newmovies.uls?page=%i" % (page+1), ("", ""), None)) + + return ugo.Pack() + + + + + + diff --git a/hatenadir/ds/v2-xx/help/post_howto.htm b/hatenadir/ds/v2-xx/help/post_howto.htm new file mode 100644 index 0000000..acaf5b2 --- /dev/null +++ b/hatenadir/ds/v2-xx/help/post_howto.htm @@ -0,0 +1 @@ +<html> <head> <title>Post Flipnote
Posting a Flipnote

Posting a flipnote is as easy as pressing the button down below and selecting the flipnote you want to upload!

\ No newline at end of file diff --git a/hatenadir/ds/v2-xx/inbox.ugo b/hatenadir/ds/v2-xx/inbox.ugo new file mode 100644 index 0000000..65d9942 Binary files /dev/null and b/hatenadir/ds/v2-xx/inbox.ugo differ diff --git a/hatenadir/ds/v2-xx/index.ugoxml b/hatenadir/ds/v2-xx/index.ugoxml new file mode 100644 index 0000000..1890ad7 --- /dev/null +++ b/hatenadir/ds/v2-xx/index.ugoxml @@ -0,0 +1,72 @@ + + + + 0 + + + <label /> + <label /> + <label /> + <label /> + <label /> + <num>0</num> + + + + + + + + + + diff --git a/hatenadir/ds/v2-xx/information.ugo b/hatenadir/ds/v2-xx/information.ugo new file mode 100644 index 0000000..4210d2d Binary files /dev/null and b/hatenadir/ds/v2-xx/information.ugo differ diff --git a/hatenadir/ds/v2-xx/movie.py b/hatenadir/ds/v2-xx/movie.py new file mode 100644 index 0000000..434cf1f --- /dev/null +++ b/hatenadir/ds/v2-xx/movie.py @@ -0,0 +1,93 @@ +from twisted.web import resource +import os + +from hatena import Database, Log, ServerLog, Silent, NotFound + +detailsHMTL = """uguu""" + + +#The movie folder: +class PyResource(resource.Resource): + isLeaf = False + def __init__(self): + resource.Resource.__init__(self) + + self.CreatorID = CreatorID() + def getChild(self, name, request): + if Database.CreatorExists(name): + return self.CreatorID + elif name == "": + return self + else: + return NotFound + def render(self, request): + request.setResponseCode(403) + return "403 - Denied access" + +#The creator ID folder: +class CreatorID(resource.Resource): + isLeaf = False + def __init__(self): + resource.Resource.__init__(self) + + self.CreatorIDFile = CreatorIDFile() + def getChild(self, name, request): + CreatorID = request.path.split("/")[-2] + filename = ".".join(name.split(".")[:-1]) + + if Database.FlipnoteExists(CreatorID, filename):#html, ppm and info + return self.CreatorIDFile + elif name == "": + return self + else: + return NotFound + def render(self, request): + request.setResponseCode(403) + return "403 - Denied access" + +#Any public file inside creator ID folder: +class CreatorIDFile(resource.Resource): + isLeaf = True + def __init__(self): + resource.Resource.__init__(self) + def render(self, request): + creator, file = request.path.split("/")[-2:] + filetype = file.split(".")[-1].lower() + + if filetype == "ppm": + #log it: + path = "/".join(request.path.split("/")[3:]) + Log(request, path) + + #add a view: + Database.AddView(creator, file[:-4]) + + #read ppm file: + f = open(Database.FlipnotePath(creator, file), "rb") + data = f.read() + f.close() + + #send file to client: + request.responseHeaders.setRawHeaders('content-type', ['text/plain']) + return data + elif filetype == "info": + path = "/".join(request.path.split("/")[3:]) + Log(request, path) + request.responseHeaders.setRawHeaders('content-type', ['text/plain']) + return "0\n0\n"#undocumented what it means + elif filetype == "htm":#not yet implemented + + + #ret = detailsHMTL[:] + #ret.replace("%%CreatorID%%", creator) + #ret.replace("%%Filename%%", file[:-4])#without the ext + #ret.replace("%%%%", ) + #ret.replace("%%%%", ) + + return "Not yet implemented" + else: + path = "/".join(request.path.split("/")[3:]) + ServerLog.write("%s got 403 when requesting " % (request.getClientIP(), path), Silent) + + request.setResponseCode(403) + return "403 - Denied access" \ No newline at end of file diff --git a/hatenadir/ds/v2-xx/movies.ugo b/hatenadir/ds/v2-xx/movies.ugo new file mode 100644 index 0000000..fd25ca2 Binary files /dev/null and b/hatenadir/ds/v2-xx/movies.ugo differ diff --git a/hatenadir/ds/v2-xx/post/flipnote.post.py b/hatenadir/ds/v2-xx/post/flipnote.post.py new file mode 100644 index 0000000..106213b --- /dev/null +++ b/hatenadir/ds/v2-xx/post/flipnote.post.py @@ -0,0 +1,32 @@ +from twisted.web import resource +from twisted.internet import reactor +import time + +from hatena import Database, ServerLog, Silent +from Hatenatools import TMB + +#Handle flipnote uploads +class PyResource(resource.Resource): + isLeaf = True + def render_GET(self, request): + ServerLog.write("%s got 403 when requesting post/flipnote.post with GET" % (request.getClientIP()), Silent) + + request.setResponseCode(405) + return "405 - Method Not Allowed" + def render_POST(self, request):#implement channels? + data = request.content.read() + + channel = "" + if "channel" in request.args: + channel = request.args["channel"][0] + + add = Database.AddFlipnote(data, channel) + if add: + ServerLog.write("%s successfully uploaded \"%s.ppm\"" % (request.getClientIP(), add[1]), Silent) + request.setResponseCode(200) + else: + ServerLog.write("%s tried to upload a flipnote, but failed..." % request.getClientIP(), Silent) + request.setResponseCode(500)#only causes an error, need to fix + + return "" + #=== \ No newline at end of file diff --git a/hatenadir/images/ds/kaeru.ntft b/hatenadir/images/ds/kaeru.ntft new file mode 100644 index 0000000..7032be8 --- /dev/null +++ b/hatenadir/images/ds/kaeru.ntft @@ -0,0 +1 @@ +’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ3ƒ \ No newline at end of file diff --git a/hatenadir/images/ds/list.ntft b/hatenadir/images/ds/list.ntft new file mode 100644 index 0000000..9fb2b0d --- /dev/null +++ b/hatenadir/images/ds/list.ntft @@ -0,0 +1 @@ +’’’’’’’’’’’’’’’’’’2ƒ2ƒ2ƒ2ƒ’’’2ƒ2ƒ2ƒ2ƒ2ƒ2ƒ’2ƒ2ƒ2ƒ’’2ƒ2ƒ2ƒ2ƒ2ƒ’’’’2ƒ2ƒ2ƒ2ƒ’’’’2ƒ2ƒ2ƒ2ƒ2ƒ’’2ƒ2ƒ2ƒ’2ƒ2ƒ2ƒ2ƒ2ƒ2ƒ’’’2ƒ2ƒ2ƒ2ƒ’’’’2ƒ2ƒ2ƒ2ƒ’’’’2ƒ2ƒ2ƒ2ƒ’’’’2ƒ2ƒ2ƒ2ƒ’’’’2ƒ2ƒ2ƒ2ƒ’’’’2ƒ2ƒ2ƒ2ƒ’’’’2ƒ2ƒ2ƒ2ƒ’’ \ No newline at end of file diff --git a/hatenadir/images/ds/thumb_down.ntft b/hatenadir/images/ds/thumb_down.ntft new file mode 100644 index 0000000..d9c7b21 --- /dev/null +++ b/hatenadir/images/ds/thumb_down.ntft @@ -0,0 +1 @@ +’’’™55555555’’’’’’™Ÿć?Ļüü?Ļ?Ļ?Ļ?Ļ?Ļ55’’’’™Ÿć?Ļ?Ļ?Ļü?ϟćŸć?Ļ?Ļ?Ļ5’’’üüüüü?Ļ?ϟćŸćŸćŸćŸć?Ļ5’’™Ÿć?Ļ?Ļ?Ļü?Ļ?Ļ?Ļ?Ļ?ϟćŸć5’’üüüüü?Ļ?Ļüüü?ϟćŸć™’’™Ÿć?Ļ?Ļ?Ļüü?Ļ?Ļ?Ļ?ϟćŸć™’’üüüüü?Ļ?Ļ?ϟćŸćŸć?Ļ?Ļ5’’™ŸćŸćŸćŸćü?ϟćŸćŸć?Ļ?Ļ5’’’’™™™™5?Ļ?Ļ?Ļ?Ļ55’’’’’’’’’5?Ļ?Ļ?Ļ5’’’’’’’’’’™?Ļ?Ļ?Ļ5’’’’’’’’’’™Ÿć?Ļ?Ļ5’’’’’’’’’’’™Ÿć?Ļ?Ļ5’’’’’’’’’’’™ŸćŸćŸć™’’’’’’’’’’’’™™™’’’’’’’ \ No newline at end of file diff --git a/logs/2014/January/26 January activity.log b/logs/2014/January/26 January activity.log new file mode 100644 index 0000000..dbc47a4 --- /dev/null +++ b/logs/2014/January/26 January activity.log @@ -0,0 +1,3 @@ +[05:04:23] Server startup... + +[05:04:25] Server shutdown diff --git a/logs/2014/January/26 January error.log b/logs/2014/January/26 January error.log new file mode 100644 index 0000000..e69de29 diff --git a/server.py b/server.py new file mode 100644 index 0000000..9f127f4 --- /dev/null +++ b/server.py @@ -0,0 +1,103 @@ +from twisted.web import server#filehost +from twisted.internet import reactor +import sys, time, os + +#Logging +class Log: + class filesplit:#a file object writing to two outputs + def __init__(self): + self.files = [] + def write(self, data): + for i in self.files: i.write(data) + def __init__(self): + minutes, seconds = map(int, time.strftime("%M %S").split(" ")) + minutes = 59 - minutes + seconds = 59 - seconds + reactor.callLater(60*minutes + seconds + 5, self.HandleUpdate) + reactor.callLater(60*5, self.AutoFlush) + + #make year folder + if not os.path.exists("logs/"+time.strftime("%Y")): + os.mkdir("logs/"+time.strftime("%Y")) + + #make month folder + if not os.path.exists("logs/"+time.strftime("%Y/%B")): + os.mkdir("logs/"+time.strftime("%Y/%B")) + + self.Activityhandle = open(time.strftime("logs/%Y/%B/%d %B activity.log"),"a") + self.Errorhandle = open(time.strftime("logs/%Y/%B/%d %B error.log"),"a") + + self.stderr = sys.stderr + sys.stderr = self.filesplit() + sys.stderr.files.append(self.stderr) + sys.stderr.files.append(self.Errorhandle) + + self.write("Server startup...\n", True) + def HandleUpdate(self):#Automatically updates the handles for new filenames every hour + reactor.callLater(60*60, self.HandleUpdate) + + print time.strftime("[%H:%M:%S] Handle update") + + #make year folder + if not os.path.exists("logs/"+time.strftime("%Y")): + os.mkdir("logs/"+time.strftime("%Y")) + + #make month folder + if not os.path.exists("logs/"+time.strftime("%Y/%B")): + os.mkdir("logs/"+time.strftime("%Y/%B")) + + self.close() + + self.Activityhandle = open(time.strftime("logs/%Y/%B/%d %B activity.log"),"a") + self.Errorhandle = open(time.strftime("logs/%Y/%B/%d %B error.log"),"a") + + sys.stderr.files[1] = self.Errorhandle + def AutoFlush(self):#Automatically flushes the files 5 minutes between + reactor.callLater(60*5, self.AutoFlush) + self.flush() + def flush(self):#Flushes to the files + self.Activityhandle.flush() + os.fsync(self.Activityhandle.fileno()) + + self.Errorhandle.flush() + os.fsync(self.Errorhandle.fileno()) + def close(self): + self.Activityhandle.close() + self.Errorhandle.close() + #===== + def write(self, String, Silent=False): + if not Silent: + print time.strftime("[%H:%M:%S]"), String + self.Activityhandle.write(time.strftime("[%H:%M:%S] ") + String + "\n") + Print = write +Log = Log() + +#Setup hatena server: +print "Setting up flipnote database..." +import hatena +hatena.ServerLog = Log +print "Setting up hatena site structure..." +site = server.Site(hatena.Setup()) + +#make the hatena server accept proxy coneections: +print "Setting up proxy hack..." +silent = True +old_lineReceived = site.protocol.lineReceived +def lineReceived(self, line): + if not silent: print line + if line[:31] == "GET http://flipnote.hatena.com/": + line = "GET " + line[30:] + if not silent: print line + elif line[:32] == "POST http://flipnote.hatena.com/": + line = "POST " + line[31:] + if not silent: print line + old_lineReceived(self, line) +site.protocol.lineReceived = lineReceived + +#run! +print "Server start!\n" +reactor.listenTCP(8080, site)#Hey listen!~ +reactor.run() + +#dunn +Log.write("Server shutdown", True) \ No newline at end of file