├── .gitignore ├── API_LIST.markdown ├── LICENSE.txt ├── README.markdown ├── REST_API.markdown ├── nautilus-minus ├── README.rst ├── minus_utils │ ├── __init__.py │ └── multipart.py └── upload_to_minus.py ├── perl ├── test.txt └── upload.pl ├── php └── MinusAPI.php └── python └── create_gallery.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.py~ 3 | *.wpr 4 | 5 | 6 | -------------------------------------------------------------------------------- /API_LIST.markdown: -------------------------------------------------------------------------------- 1 | The Minus API 2 | ============= 3 | This list contains links to the Minus API documentation and API wrappers in various languages. 4 | 5 | API Documentation 6 | ================= 7 | [https://github.com/Miners/MinusAPI/blob/master/REST_API.markdown](https://github.com/Miners/MinusAPI/blob/master/REST_API.markdown) 8 | 9 | Upload With cURL 10 | ============= 11 | Note that the editor_id does not have the leading m that is in the page url. 12 | curl "http://min.us/api/UploadItem?editor_id=dn48vKBiP3q9&key=OK&filename=min.png" -F "file=@min.png" 13 | 14 | [http://min.us/mvwUFP#1](Example) 15 | 16 | C# 17 | == 18 | [https://github.com/Miners/MinusEngine](https://github.com/Miners/MinusEngine) 19 | 20 | Python 21 | ======= 22 | [http://code.google.com/p/python-minus/](http://code.google.com/p/python-minus/) 23 | 24 | Javascript 25 | ======= 26 | [https://github.com/buger/minus-javascript](https://github.com/buger/minus-javascript) -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2010-2011 Minus Inc. 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | 192 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | The Minus API 2 | ============= 3 | We use this GitHub repository to collect information and utilities to help people who want to build applications for Minus (http://min.us), a sharing platform, to build more easily. 4 | 5 | 6 | REST API Documentation 7 | ================= 8 | [https://github.com/Miners/MinusAPI/blob/master/REST_API.markdown](https://github.com/Miners/MinusAPI/blob/master/REST_API.markdown) 9 | 10 | Upload With cURL 11 | ============= 12 | Note that the editor_id does not have the leading m that is in the page url. 13 | curl "http://min.us/api/UploadItem?editor_id=dn48vKBiP3q9&key=OK&filename=min.png" -F "file=@min.png" 14 | 15 | [http://min.us/mvwUFP#1](Example) 16 | 17 | C# 18 | == 19 | [https://github.com/Miners/MinusEngine](https://github.com/Miners/MinusEngine) 20 | 21 | Python 22 | ======= 23 | [http://code.google.com/p/python-minus/](http://code.google.com/p/python-minus/) 24 | 25 | Javascript 26 | ======= 27 | [https://github.com/buger/minus-javascript](https://github.com/buger/minus-javascript) 28 | 29 | 30 | We welcome your contributions, issue reportage, and suggestions. Write us at info@min.us, or contribute directly here! 31 | ======= 32 | 33 | --- 34 | Carl Hu | Cofounder 35 | carl@min.us | http://min.us 36 | Share simply. -------------------------------------------------------------------------------- /REST_API.markdown: -------------------------------------------------------------------------------- 1 | The REST API 2 | ============ 3 | 4 | Overview 5 | ======== 6 | At the lowest level, Minus exposes its API with a REST, http-accessible, API. To make it easier for application builders, we will be providing Python, .NET, Ruby, and other API's in the near future. 7 | 8 | This file documents the REST API. 9 | 10 | The API 11 | ======= 12 | We have designed our API to allow you to easily create galleries and upload files programmatically. There are currently 7 API’s published with more to come in the near future. The API is open and no registration is required. 13 | 14 | Please subscribe to our http://blog.min.us for any updates regarding our API and contact us if you have any questions. 15 | 16 | We have designed our API to allow you to easily create galleries and upload files programmatically. There are currently 7 API’s published with more to come in the near future. The API is open and no registration is required. 17 | 18 | Please subscribe to our http://blog.min.us for any updates regarding our API and contact us if you have any questions. 19 | 20 | CreateGallery 21 | ==================== 22 | 23 | Description 24 | ---------------- 25 | * http://min.us/api/CreateGallery 26 | * creates a gallery on the server side and returns the editor and reader id. The editor id combined with the key can be used to upload images into the gallery. 27 | * On return, the gallery can be immediately viewed on the web site. 28 | 29 | Example 30 | ------------- 31 | * A call returns the below HttpResponse. This gallery can be edited in any browser by going to http://min.us/mej0rg. The gallery can be viewed via http://min.us/vodiX5. Response: {"editor_id": "ej0rg", "reader_id": "vodiX5"} 32 | 33 | UploadItem 34 | =================== 35 | 36 | * http://min.us/api/UploadItem 37 | 38 | Parameters 39 | -------------- 40 | * URL should be structured as follows: http://min.us/api/UploadItem?editor_id=enRlK&key=OK&filename=jcm3U.png 41 | * Body: A standard multipart file POST. 42 | 43 | Result 44 | ---------- 45 | You can immediately view this picture in any browser by: http://min.us/icBpkM. Returns: {"id": "cBpkM", "height": 64, "width": 500, "filesize": "1010 bytes"} 46 | 47 | Note that you must prefix the id with a /i (if it's an item) or a /m (if it's a gallery) to see access the uploaded item or new gallery. 48 | 49 | Example 50 | ---------- 51 | Note that the editor_id does not have the leading m that is in the page url. 52 | curl "http://min.us/api/UploadItem?editor_id=dn48vKBiP3q9&key=OK&filename=min.png" -F "file=@min.png" 53 | http://min.us/mvwUFP#1 54 | 55 | SaveGallery 56 | =========== 57 | * http://min.us/api/SaveGallery 58 | * Use this to update the gallery name or change sort order. 59 | * application/x-www-form-urlencoded 60 | * items=%5B%22A1Q%22%5D&name=test2&key=OK&id=bgZrGCaapOSL2 61 | * items is a json encoded list of the gallery items: {"A1Q", "B1Q"} 62 | Example working Python code: 63 | 64 | :: 65 | 66 | import httplib, urllib, urllib2, json 67 | params = {"name":"test2","id":"bgZrGCaapOSL2","key":"OK","items":json.dumps(["A1Q"])} 68 | encoded = urllib.urlencode(params) 69 | f = urllib2.urlopen('http://min.us/api/SaveGallery ', encoded) 70 | 71 | GetItems 72 | ============ 73 | * http://min.us/api/GetItems 74 | * Use this to retrieve items inside gallery programmatically 75 | 76 | Example: http://min.us/api/GetItems/mvgkRZC returns: 77 | :: 78 | 79 | { 80 | "READ_ONLY_URL_FOR_GALLERY": "http://min.us/mvgkRZC", 81 | "GALLERY_TITLE": "Untitled", 82 | "ITEMS_GALLERY": [ 83 | "http://i.min.us/jb0oQi", 84 | "http://i.min.us/jb0Aj2", 85 | "http://i.min.us/jdLnSy", 86 | "http://i.min.us/jdH3Au", 87 | "http://i.min.us/jdLqa6" 88 | ] 89 | } 90 | 91 | SignIn 92 | ============ 93 | * http://min.us/api/SignIn 94 | * Use this to sign in a user and get a cookie to use to add galleries to the user's "My Galleries" page. 95 | * POST request type, parameters are **username** and **password1**, with the already registered username and password for the user. 96 | * The reply is a json of a dictionary with a key you should check: "success": True/False. Other values in the dictionary give error information telling the user about invalid username, password, or username-password combination. 97 | * Retain cookie to place subsequent visited or created galleries in the user's "My Galleries" page. 98 | 99 | SignOut 100 | ============ 101 | * http://min.us/api/SignOut 102 | * GET request. No params. 103 | * Clears cookie, signs out user. 104 | 105 | My Galleries 106 | ============ 107 | * http://min.us/api/MyGalleries.json 108 | * GET request. No params. 109 | * Exposes My Galleries page as a convenient json call. 110 | * If the edit url is not available, instead of starting with 'm', it will be "Unavailable". If the gallery has been deleted, the value is "Deleted". 111 | 112 | JSON formatted list: 113 | 114 | { 115 | "galleries": [ 116 | { "last_visit": "7 minutes ago", "name": "test", "item_count": 1, "clicks": 4, "reader_id": "vgkRZC", "editor_id": "bgZrGCaapOSL2" }, 117 | { "last_visit": "5 hours ago", "name": "test2", "item_count": 1, "clicks": 0, "reader_id": "vgkRZB", "editor_id": "bgZrGCaapOSL1" } 118 | ] 119 | } 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /nautilus-minus/README.rst: -------------------------------------------------------------------------------- 1 | ABOUT 2 | ##### 3 | 4 | upload_to_minus.py: part of nautilus-minus package - Upload image or more 5 | images at once to http://min.us. Each batch upload makes it's own gallery. Only 6 | images supported. Adds context menu item that allows uploading. It uses pynotify 7 | - libnotify if availible, and if not, tries to inform the user of possible errors, 8 | updates, with zenity. Try to have at least 1 of these packages installed. Ubuntu 9 | and Ubuntu forks have libnotify by default. 10 | 11 | INSTALLATION 12 | ############ 13 | 14 | 1. Use the deb package. Should be in github download section. 15 | 16 | dpkg -i package_name.deb 17 | 18 | Remember to restart nautilus: 19 | 20 | nautilus -q or 21 | killall nautilus 22 | 23 | This installs all dependencies, so you don't have to worry about anything else. 24 | 25 | 2. Copy all files from the package into $HOME/.nautilus/python-extensions/ 26 | 27 | Install python-nautilus package (http://projects.gnome.org/nautilus-python/): 28 | 29 | sudo apt-get install python-nautilus - for Ubuntu/Ubuntu forks 30 | 31 | Restart nautilus: 32 | 33 | nautilus -q 34 | 35 | 36 | USAGE 37 | ##### 38 | 39 | Right click on an image or selection of images -> Upload to min.us. 40 | Wait for it... 41 | 42 | -------------------------------------------------------------------------------- /nautilus-minus/minus_utils/__init__.py: -------------------------------------------------------------------------------- 1 | # multipart is an LGPL library. I didn't want to clutter the code here 2 | # so I put it in an outside script. File uploads with python's stdlib 3 | # sucks big time. This lib defines a MultiPart handler. 4 | from multipart import * 5 | 6 | def notify(message): 7 | """ 8 | Notify On Screen for possible errors and actions. 9 | Tries to use libnotify, and falls back to zenity. 10 | """ 11 | try: 12 | import pynotify 13 | pynotify.Notification(message).show() 14 | return 15 | except ImportError, e: 16 | pass 17 | os.popen("zenity --info --text='%s'"%message) 18 | 19 | def path_from_uri(file_uri): 20 | """ 21 | Nautilus returns 'file://[path]' uri format. 22 | We don't need the file:// handle. 23 | """ 24 | return file_uri[7:] 25 | 26 | -------------------------------------------------------------------------------- /nautilus-minus/minus_utils/multipart.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | #### 4 | # 02/2006 Will Holcomb 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This library is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # Lesser General Public License for more details. 15 | # 16 | # 7/26/07 Slightly modified by Brian Schneider 17 | # in order to support unicode files ( multipart_encode function ) 18 | """ 19 | Usage: 20 | Enables the use of multipart/form-data for posting forms 21 | 22 | Inspirations: 23 | Upload files in python: 24 | http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 25 | urllib2_file: 26 | Fabien Seisen: 27 | 28 | Example: 29 | import MultipartPostHandler, urllib2, cookielib 30 | 31 | cookies = cookielib.CookieJar() 32 | opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), 33 | MultipartPostHandler.MultipartPostHandler) 34 | params = { "username" : "bob", "password" : "riviera", 35 | "file" : open("filename", "rb") } 36 | opener.open("http://wwww.bobsite.com/upload/", params) 37 | 38 | Further Example: 39 | The main function of this file is a sample which downloads a page and 40 | then uploads it to the W3C validator. 41 | """ 42 | 43 | import urllib 44 | import urllib2 45 | import mimetools, mimetypes 46 | import os, stat 47 | from cStringIO import StringIO 48 | 49 | class Callable: 50 | def __init__(self, anycallable): 51 | self.__call__ = anycallable 52 | 53 | # Controls how sequences are uncoded. If true, elements may be given multiple values by 54 | # assigning a sequence. 55 | doseq = 1 56 | 57 | class MultipartPostHandler(urllib2.BaseHandler): 58 | handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first 59 | 60 | def http_request(self, request): 61 | data = request.get_data() 62 | if data is not None and type(data) != str: 63 | v_files = [] 64 | v_vars = [] 65 | try: 66 | for(key, value) in data.items(): 67 | if type(value) == file: 68 | v_files.append((key, value)) 69 | else: 70 | v_vars.append((key, value)) 71 | except TypeError: 72 | systype, value, traceback = sys.exc_info() 73 | raise TypeError, "not a valid non-string sequence or mapping object", traceback 74 | 75 | if len(v_files) == 0: 76 | data = urllib.urlencode(v_vars, doseq) 77 | else: 78 | boundary, data = self.multipart_encode(v_vars, v_files) 79 | 80 | contenttype = 'multipart/form-data; boundary=%s' % boundary 81 | if(request.has_header('Content-Type') 82 | and request.get_header('Content-Type').find('multipart/form-data') != 0): 83 | print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data') 84 | request.add_unredirected_header('Content-Type', contenttype) 85 | 86 | request.add_data(data) 87 | 88 | return request 89 | 90 | def multipart_encode(vars, files, boundary = None, buf = None): 91 | if boundary is None: 92 | boundary = mimetools.choose_boundary() 93 | if buf is None: 94 | buf = StringIO() 95 | for(key, value) in vars: 96 | buf.write('--%s\r\n' % boundary) 97 | buf.write('Content-Disposition: form-data; name="%s"' % key) 98 | buf.write('\r\n\r\n' + value + '\r\n') 99 | for(key, fd) in files: 100 | file_size = os.fstat(fd.fileno())[stat.ST_SIZE] 101 | filename = fd.name.split('/')[-1] 102 | contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' 103 | buf.write('--%s\r\n' % boundary) 104 | buf.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename)) 105 | buf.write('Content-Type: %s\r\n' % contenttype) 106 | # buffer += 'Content-Length: %s\r\n' % file_size 107 | fd.seek(0) 108 | buf.write('\r\n' + fd.read() + '\r\n') 109 | buf.write('--' + boundary + '--\r\n\r\n') 110 | buf = buf.getvalue() 111 | return boundary, buf 112 | multipart_encode = Callable(multipart_encode) 113 | 114 | https_request = http_request 115 | 116 | def main(): 117 | import tempfile, sys 118 | 119 | validatorURL = "http://validator.w3.org/check" 120 | opener = urllib2.build_opener(MultipartPostHandler) 121 | 122 | def validateFile(url): 123 | temp = tempfile.mkstemp(suffix=".html") 124 | os.write(temp[0], opener.open(url).read()) 125 | params = { "ss" : "0", # show source 126 | "doctype" : "Inline", 127 | "uploaded_file" : open(temp[1], "rb") } 128 | print opener.open(validatorURL, params).read() 129 | os.remove(temp[1]) 130 | 131 | if len(sys.argv[1:]) > 0: 132 | for arg in sys.argv[1:]: 133 | validateFile(arg) 134 | else: 135 | validateFile("http://www.google.com") 136 | 137 | if __name__=="__main__": 138 | main() 139 | 140 | 141 | -------------------------------------------------------------------------------- /nautilus-minus/upload_to_minus.py: -------------------------------------------------------------------------------- 1 | #!/usr/env python 2 | 3 | # upload_to_minus.py - Nautilus Extension for uploading image or 4 | # batch of images to http://min.us. 5 | # Copyright (C) 2010 Dejan Noveski 6 | # 7 | # Permission is hereby granted, free of charge, to any person 8 | # obtaining a copy of this software and associated documentation files 9 | # (the "Software"), to deal in the Software without restriction, 10 | # including without limitation the rights to use, copy, modify, merge, 11 | # publish, distribute, sublicense, and/or sell copies of the Software, 12 | # and to permit persons to whom the Software is furnished to do so, 13 | # subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be 16 | # included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 22 | # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 23 | # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 24 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | 27 | """ 28 | Check README.rst 29 | """ 30 | 31 | import gconf 32 | import nautilus 33 | import urllib, urllib2 34 | import mimetools, mimetypes 35 | import os, stat 36 | from StringIO import StringIO 37 | from exceptions import ImportError 38 | import simplejson as json 39 | from minus_utils import * 40 | 41 | # Any other mimetypes i forgot? 42 | SUPPORTED_FORMATS = ('image/jpeg', 43 | 'image/png', 44 | 'image/gif', 45 | 'image/bmp',) 46 | 47 | # Min.us urls 48 | MINUS_URL = "http://min.us/" 49 | API_URL = MINUS_URL + "api/" 50 | GALLERY_URL = API_URL + "CreateGallery" 51 | UPLOAD_URL = API_URL + "UploadItem" 52 | 53 | class MinusUploaderExtension(nautilus.MenuProvider): 54 | 55 | """ 56 | Minus Uploader provider class - Adds an item in the contex menu in 57 | nautilus for image mimetypes. 58 | """ 59 | def __init__(self): 60 | self.gconf = gconf.client_get_default() 61 | 62 | def menu_activate(self, menu, files): 63 | """ Callback for menu item activate """ 64 | return self.upload_gallery(files) 65 | 66 | def get_file_items(self, window, files): 67 | """ Shows the menu item """ 68 | if len(files) == 0: 69 | return 70 | 71 | for f in files: 72 | if not f.get_mime_type() in SUPPORTED_FORMATS: 73 | return 74 | if f.get_uri_scheme() != 'file': 75 | return 76 | 77 | item = nautilus.MenuItem('Nautilus::upload_to_min_us', 78 | 'Upload to min.us', 79 | 'Upload to min.us', 80 | 'up') 81 | # connect to callback 82 | item.connect('activate', self.menu_activate, files) 83 | return item, 84 | 85 | def upload_gallery(self, files): 86 | """ Uploads selected images to imgur """ 87 | # create a gallery - minus works like that 88 | gallery = self.create_gallery() 89 | if gallery: 90 | editor_id = gallery["editor_id"] 91 | reader_id = gallery["reader_id"] 92 | 93 | for f in files: 94 | # use the created gallery and add the images there 95 | self.upload_image(f.get_uri(), editor_id, reader_id) 96 | 97 | try: 98 | # Open the default browser and show the gallery 99 | import webbrowser 100 | webbrowser.open(MINUS_URL + "m" + editor_id) 101 | except ImportError, e: 102 | # No browser? Show the url in notification. 103 | notify(MINUS_URL + "m" + editor_id) 104 | 105 | def upload_image(self, image, editor_id, reader_id): 106 | 107 | try: 108 | image_path = path_from_uri(image) 109 | params = { 110 | "file": open(image_path, "rb")} 111 | notify("Uploading %s to min.us"%os.path.basename(image_path)) 112 | urlopener = urllib2.build_opener(MultipartPostHandler) 113 | response = urlopener.open( 114 | "%s?key=%s&editor_id=%s&filename=%s" % 115 | (UPLOAD_URL, "nautilus-uploader-"+editor_id, 116 | editor_id, os.path.basename(image_path),), 117 | params) 118 | except URLError, e: 119 | notify(e.reason) 120 | 121 | def create_gallery(self): 122 | request = urllib2.Request(GALLERY_URL) 123 | try: 124 | response = urllib2.urlopen(request) 125 | except urllib2.URLError, e: 126 | notify(e.reason) 127 | return None 128 | 129 | return json.loads(response.read()) 130 | 131 | -------------------------------------------------------------------------------- /perl/test.txt: -------------------------------------------------------------------------------- 1 | test 123 -------------------------------------------------------------------------------- /perl/upload.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use JSON; 6 | use HTTP::Request; 7 | use HTTP::Request::Common; 8 | use HTTP::Cookies; 9 | use LWP::UserAgent; 10 | use File::Basename; 11 | 12 | my $user = "fill_here"; #Username 13 | my $pass = "fill_here"; #Password 14 | 15 | sub usage { 16 | print " -u : upload item\n -i : get item by id\n -g: list galleries\n"; 17 | } 18 | 19 | @ARGV || die &usage ; 20 | 21 | my $browser = LWP::UserAgent->new; 22 | $browser->timeout(15); 23 | 24 | my $cookie_jar = HTTP::Cookies->new( 25 | file => 'cookies.txt', 26 | autosave => 1, 27 | ); 28 | $cookie_jar->save('cookies.txt'); 29 | 30 | $browser->cookie_jar($cookie_jar); 31 | 32 | 33 | #SignIn 34 | my $req_login = HTTP::Request->new(POST => "http://min.us/api/SignIn"); 35 | $req_login->content_type("application/x-www-form-urlencoded"); 36 | $req_login->content("username=" . $user . "&password1=" . $pass); 37 | 38 | 39 | sub GetItem { 40 | 41 | $browser->request($req_login)->as_string; #login 42 | 43 | chomp(my $id = $ARGV[1] ); 44 | 45 | my $req_getitem = HTTP::Request->new(GET=> "http://min.us/api/GetItems/m$id"); #adding an m here 46 | $req_getitem->content_type("application/x-www-form-urlencoded"); 47 | 48 | if ($browser->request($req_getitem)->is_success) { 49 | 50 | my $json = $browser->request($req_getitem)->content; 51 | my $json_coder = JSON::XS->new->utf8; 52 | my %json_perl = %{ $json_coder->decode($json) }; 53 | 54 | foreach my $key ( sort keys %json_perl ) { 55 | print "$key\n"; 56 | if (ref ($json_perl{$key}) eq "ARRAY" ) { 57 | foreach ( @{ $json_perl{$key} } ) { #dereferencing array 58 | print $_ . "\n"; 59 | } 60 | } else { 61 | print $json_perl{$key}."\n"; 62 | } 63 | 64 | } 65 | 66 | 67 | } else { 68 | print "Request not successful\n"; 69 | print $browser->request($req_getitem)->status_line, "\n"; 70 | } 71 | 72 | } 73 | 74 | sub MyGalleries { 75 | 76 | $browser->request($req_login)->as_string; #login 77 | 78 | my $req_mygalleries = HTTP::Request->new(GET=> "http://min.us/api/MyGalleries.json"); 79 | $req_mygalleries->content_type("text/html"); 80 | 81 | if ($browser->request($req_mygalleries)->is_success) { 82 | 83 | my $json = $browser->request($req_mygalleries)->content; 84 | my $json_coder = JSON::XS->new->utf8->pretty->allow_nonref; 85 | my %json_perl = %{ $json_coder->decode($json) }; 86 | 87 | foreach my $key ( sort keys %json_perl ) { 88 | print "$key\n\n"; 89 | 90 | if (ref ($json_perl{$key}) eq "ARRAY" ) { 91 | foreach ( @{ $json_perl{$key} } ) { #dereferencing array of hashes 92 | print "\n"; 93 | foreach ( %{ $_ } ) { #dereferencing hashes 94 | print $_ . "\n" ; 95 | } 96 | } 97 | } else { 98 | print $json_perl{$key}."\n"; 99 | } 100 | 101 | } 102 | 103 | 104 | } else { 105 | print "Request not successful\n"; 106 | print $browser->request($req_mygalleries)->status_line, "\n"; 107 | } 108 | 109 | } 110 | 111 | 112 | sub UploadItem { 113 | 114 | #Note that the editor_id has the leading m removed 115 | 116 | chomp( my $editor_id = $ARGV[1] ); 117 | chomp( my $filename = $ARGV[2] ); 118 | 119 | unless (-e $filename) { die "$filename: $!"; } 120 | 121 | my $basename = basename($filename); 122 | my $length = -s $filename; 123 | 124 | my $url = "http://204.236.229.205/api/UploadItem?". "editor_id=" . $editor_id . "&key=OK&filename=" . $basename; 125 | 126 | $HTTP::Request::Common::DYNAMIC_FILE_UPLOAD = 1; 127 | 128 | my $req_upitem = POST( 129 | $url, 130 | Content_Type => 'multipart/form-data', 131 | Content => [ file => [$filename] ], 132 | ); 133 | 134 | my $res = $browser->request($req_upitem); 135 | 136 | if ($res->status_line =~ m/^200/ ) { 137 | print "Successfully uploaded\n\n"; 138 | my $json = $res->content; 139 | my $json_coder = JSON::XS->new->utf8->pretty->allow_nonref; 140 | my %json_perl = %{ $json_coder->decode($json) }; 141 | 142 | foreach my $key ( sort keys %json_perl ) { 143 | print "$key\n"; 144 | print $json_perl{$key}."\n"; 145 | } 146 | 147 | } else { 148 | print "Request not successful\n"; 149 | print $res->status_line, "\n"; 150 | } 151 | 152 | } 153 | 154 | 155 | if ($ARGV[0] eq "-u") { 156 | if (@ARGV == 3) { &UploadItem } else { &usage } 157 | } elsif ($ARGV[0] eq "-i") { 158 | if (@ARGV == 2) { &GetItem } else { &usage } 159 | } elsif ($ARGV[0] eq "-g") { 160 | &MyGalleries 161 | } else { 162 | &usage 163 | } 164 | 165 | 166 | exit; 167 | -------------------------------------------------------------------------------- /php/MinusAPI.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class MinusAPI 10 | { 11 | private $_cookies; 12 | 13 | public function __construct() 14 | { 15 | $this->_cookies=tempnam(sys_get_temp_dir(),'minus'); 16 | if($this->_cookies===false) 17 | throw new Exception('Error creating temporary file.'); 18 | } 19 | 20 | protected function call($service,$postfields=array()) 21 | { 22 | $ch=curl_init(); 23 | curl_setopt($ch,CURLOPT_URL,'http://min.us/api/'.$service); 24 | curl_setopt($ch,CURLOPT_COOKIEJAR,$this->_cookies); 25 | curl_setopt($ch,CURLOPT_COOKIEFILE,$this->_cookies); 26 | curl_setopt($ch,CURLOPT_HEADER,false); 27 | curl_setopt($ch,CURLOPT_RETURNTRANSFER,true); 28 | curl_setopt($ch,CURLOPT_VERBOSE,false); 29 | if(count($postfields)>0) 30 | { 31 | curl_setopt($ch,CURLOPT_POST,true); 32 | curl_setopt($ch,CURLOPT_POSTFIELDS,$postfields); 33 | } 34 | $retval=curl_exec($ch); 35 | $errno=curl_errno($ch); 36 | $error=curl_error($ch); 37 | curl_close($ch); 38 | if($errno!=0) 39 | throw new Exception($error); 40 | return json_decode($retval); 41 | } 42 | 43 | public function CreateGallery() 44 | { 45 | return $this->call(__FUNCTION__); 46 | } 47 | 48 | public function GetItems($id) 49 | { 50 | return $this->call(__FUNCTION__.'/m'.$id); 51 | } 52 | 53 | public function MyGalleries() 54 | { 55 | return $this->call(__FUNCTION__.'.json'); 56 | } 57 | 58 | public function SaveGallery($id,$name,$items,$key='OK') 59 | { 60 | return $this->call(__FUNCTION__,array( 61 | 'id'=>$id, 62 | 'name'=>$name, 63 | 'key'=>$key, 64 | 'items'=>json_encode($items), 65 | )); 66 | } 67 | 68 | public function SignIn($username,$password) 69 | { 70 | return $this->call(__FUNCTION__,array( 71 | 'username'=>$username, 72 | 'password1'=>$password, 73 | )); 74 | } 75 | 76 | public function SignOut() 77 | { 78 | return $this->call(__FUNCTION__); 79 | } 80 | 81 | public function UploadItem($id,$name,$path,$key='OK') 82 | { 83 | return $this->call(__FUNCTION__.'?'.http_build_query(array( 84 | 'editor_id'=>$id, 85 | 'filename'=>$name, 86 | 'key'=>$key, 87 | )),array( 88 | 'file'=>"@{$path}", 89 | )); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /python/create_gallery.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python 2 | 3 | import sys, subprocess, urllib, urllib2, os 4 | from time import strftime 5 | import json 6 | import unicodedata 7 | 8 | ''' 9 | REPLACE FOLLOWING WITH VALID EDITOR AND VIEWER URLS 10 | ''' 11 | MINUS_URL = 'http://min.us/' 12 | # MINUS_URL = 'http://192.168.0.190:8001/' 13 | READER_ID = "veTYOJ" 14 | EDITOR_ID = "cXLjZ5CjJr5J" 15 | 16 | 17 | def upload_file(filename, editor_id, key): 18 | basename = os.path.basename(filename) 19 | url ='%sapi/UploadItem?editor_id=%s&key=%s&filename=%s' % (MINUS_URL, editor_id, key, basename) 20 | file_arg = 'file=@%s' % filename 21 | p = subprocess.Popen(['curl', '-s', '-S', '-F', file_arg, url], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 22 | output,error = p.communicate() 23 | if p.returncode == 0: 24 | dd = parseDict(output) 25 | print 'Uploaded %s (%s)' % (basename, dd['filesize']) 26 | else: 27 | print 'Failed %s : %s' % (basename, error) 28 | 29 | def parseDict(dictStr): 30 | dictStr = dictStr.translate(None,'\"{}') 31 | items = [s for s in dictStr.split(', ') if s] 32 | dd = {} 33 | for item in items: 34 | key,value = item.split(': ') 35 | dd[key] = value 36 | 37 | return dd 38 | 39 | def create_gallery(): 40 | f = urllib2.urlopen('%sapi/CreateGallery' % MINUS_URL) 41 | result = f.read() 42 | dd = parseDict(result) 43 | 44 | return (dd['editor_id'], "", dd['reader_id']) 45 | 46 | def saveGallery(name, editor_id, items): 47 | name = "test" 48 | params = { "name" : name, "id" : editor_id, "key" : "OK", "items" : json.dumps(items) } 49 | params = urllib.urlencode(params) 50 | try: 51 | f = urllib2.urlopen('%sapi/SaveGallery' % MINUS_URL, params) 52 | except urllib2.HTTPError, e: 53 | print "\n", e.code 54 | print "SaveGallery Failed:\n", "params: ", params 55 | 56 | def generateImageList(reader_id): 57 | 58 | formattedList = [] 59 | f = urllib2.urlopen('%sapi/GetItems/m%s' % (MINUS_URL, reader_id)) 60 | jsonData = json.loads( f.read()) 61 | imagesList = jsonData[u'ITEMS_GALLERY'] 62 | for image in imagesList: 63 | image = unicodedata.normalize('NFKD', image).encode('ascii','ignore') 64 | image = image[17:] 65 | image = image.split(".")[0] 66 | formattedList.append(image) 67 | return formattedList 68 | 69 | 70 | def main(): 71 | #editor_id,key,reader_id = create_gallery() 72 | #args = sys.argv[1:] 73 | #for arg in args: 74 | # upload_file(arg,editor_id,key) 75 | imageList = generateImageList(READER_ID) 76 | saveGallery("Testing rename - %s" % strftime("%Y-%m-%d %H:%M"), EDITOR_ID, imageList) 77 | print 'Editor URL: http://min.us/m%s' % EDITOR_ID 78 | print 'Viewer URL: http://min.us/m%s' % READER_ID 79 | 80 | if __name__ == '__main__': 81 | main() --------------------------------------------------------------------------------